@agentwonderland/mcp 0.1.23 → 0.1.24

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 (74) hide show
  1. package/dist/core/__tests__/card-setup.test.d.ts +1 -0
  2. package/dist/core/__tests__/card-setup.test.js +99 -0
  3. package/dist/core/__tests__/formatters.test.d.ts +1 -0
  4. package/dist/core/__tests__/formatters.test.js +15 -0
  5. package/dist/core/__tests__/passes.test.d.ts +1 -0
  6. package/dist/core/__tests__/passes.test.js +82 -0
  7. package/dist/core/__tests__/payments.test.d.ts +1 -0
  8. package/dist/core/__tests__/payments.test.js +52 -0
  9. package/dist/core/__tests__/principal.test.d.ts +1 -0
  10. package/dist/core/__tests__/principal.test.js +67 -0
  11. package/dist/core/api-client.d.ts +9 -4
  12. package/dist/core/api-client.js +52 -22
  13. package/dist/core/card-setup.d.ts +20 -13
  14. package/dist/core/card-setup.js +85 -29
  15. package/dist/core/config.d.ts +3 -0
  16. package/dist/core/config.js +24 -2
  17. package/dist/core/formatters.d.ts +2 -0
  18. package/dist/core/formatters.js +5 -1
  19. package/dist/core/index.d.ts +2 -0
  20. package/dist/core/index.js +2 -0
  21. package/dist/core/ows-adapter.d.ts +10 -2
  22. package/dist/core/ows-adapter.js +54 -10
  23. package/dist/core/passes.d.ts +40 -0
  24. package/dist/core/passes.js +32 -0
  25. package/dist/core/payments.d.ts +8 -0
  26. package/dist/core/payments.js +97 -13
  27. package/dist/core/principal.d.ts +2 -0
  28. package/dist/core/principal.js +109 -0
  29. package/dist/core/solana-charge.d.ts +9 -0
  30. package/dist/core/solana-charge.js +95 -0
  31. package/dist/core/types.d.ts +10 -0
  32. package/dist/index.js +8 -3
  33. package/dist/prompts/index.js +1 -1
  34. package/dist/resources/wallet.js +8 -1
  35. package/dist/tools/__tests__/_payment-confirmation.test.d.ts +1 -0
  36. package/dist/tools/__tests__/_payment-confirmation.test.js +30 -0
  37. package/dist/tools/_payment-confirmation.d.ts +6 -0
  38. package/dist/tools/_payment-confirmation.js +28 -0
  39. package/dist/tools/agent-info.js +14 -0
  40. package/dist/tools/index.d.ts +1 -0
  41. package/dist/tools/index.js +1 -0
  42. package/dist/tools/passes.d.ts +2 -0
  43. package/dist/tools/passes.js +157 -0
  44. package/dist/tools/run.js +113 -51
  45. package/dist/tools/solve.js +102 -44
  46. package/dist/tools/wallet.js +85 -50
  47. package/package.json +3 -1
  48. package/src/core/__tests__/card-setup.test.ts +118 -0
  49. package/src/core/__tests__/formatters.test.ts +17 -0
  50. package/src/core/__tests__/passes.test.ts +94 -0
  51. package/src/core/__tests__/payments.test.ts +60 -0
  52. package/src/core/__tests__/principal.test.ts +87 -0
  53. package/src/core/api-client.ts +70 -23
  54. package/src/core/card-setup.ts +109 -34
  55. package/src/core/config.ts +29 -3
  56. package/src/core/formatters.ts +7 -1
  57. package/src/core/index.ts +2 -0
  58. package/src/core/ows-adapter.ts +74 -8
  59. package/src/core/passes.ts +74 -0
  60. package/src/core/payments.ts +113 -13
  61. package/src/core/principal.ts +128 -0
  62. package/src/core/solana-charge.ts +149 -0
  63. package/src/core/types.ts +10 -0
  64. package/src/index.ts +8 -3
  65. package/src/prompts/index.ts +1 -1
  66. package/src/resources/wallet.ts +8 -1
  67. package/src/tools/__tests__/_payment-confirmation.test.ts +45 -0
  68. package/src/tools/_payment-confirmation.ts +52 -0
  69. package/src/tools/agent-info.ts +23 -0
  70. package/src/tools/index.ts +1 -0
  71. package/src/tools/passes.ts +234 -0
  72. package/src/tools/run.ts +171 -55
  73. package/src/tools/solve.ts +149 -56
  74. package/src/tools/wallet.ts +102 -52
@@ -1,11 +1,13 @@
1
1
  import { z } from "zod";
2
2
  import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
3
- import { initiateCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
4
- import { hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, getWalletAddress, } from "../core/payments.js";
3
+ import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
4
+ import { getCompatiblePaymentMethods, hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, getWalletAddress, normalizePaymentMethod, toRegistryPaymentMethod, } from "../core/payments.js";
5
5
  import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
6
6
  import { agentList, formatRunResult } from "../core/formatters.js";
7
7
  import { uploadLocalFiles } from "../core/file-upload.js";
8
8
  import { storeFeedbackToken } from "./_token-cache.js";
9
+ import { formatPaymentChoicePrompt, formatPaymentLabel, formatSolveConfirmationCommand, makeSolvePendingKey, resolveConfirmationMethod, } from "./_payment-confirmation.js";
10
+ import { getActiveCreditPack, getCreditPackInventory, getCreditPackProgram } from "../core/passes.js";
9
11
  const POLL_INTERVAL_MS = 3000;
10
12
  const POLL_MAX_MS = 120000;
11
13
  async function pollSolveJob(jobId) {
@@ -24,7 +26,7 @@ async function pollSolveJob(jobId) {
24
26
  }
25
27
  }
26
28
  catch {
27
- // Ignore poll errors, keep trying
29
+ // Ignore poll errors and keep trying.
28
30
  }
29
31
  }
30
32
  return { status: "failed", error_code: "POLL_TIMEOUT" };
@@ -35,11 +37,12 @@ function text(t) {
35
37
  function multiText(...blocks) {
36
38
  return { content: blocks.map((t) => ({ type: "text", text: t })) };
37
39
  }
40
+ const pendingSolves = new Map();
38
41
  function feedbackAsk(jobId, agentId, cost, tipSent) {
39
42
  const defaultTip = getDefaultTipAmount();
40
- const costLine = cost != null ? `Paid $${cost.toFixed(cost < 0.01 ? 4 : 2)}.` : "";
43
+ const costLine = cost != null ? `Paid $${cost.toFixed(cost < 0.01 ? 4 : 2)}.` : "This run used your available credit pack.";
41
44
  return [
42
- `IMPORTANT: After presenting the result above, you MUST ask the user:`,
45
+ "IMPORTANT: After presenting the result above, you MUST ask the user:",
43
46
  `"${costLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
44
47
  tipSent ?? "",
45
48
  "",
@@ -67,8 +70,22 @@ async function autoTip(jobId, agentId, agentName, feedbackToken) {
67
70
  return `Auto-tip of $${defaultTip.toFixed(2)} failed.`;
68
71
  }
69
72
  }
73
+ function buildCreditPackSummary(agent) {
74
+ const creditPacks = getCreditPackProgram(agent);
75
+ if (!creditPacks?.packs?.length)
76
+ return [];
77
+ return [
78
+ "Discounted credit packs available:",
79
+ ...creditPacks.packs.map((pack) => {
80
+ const price = Number(pack.price_usd ?? "0");
81
+ const units = pack.included_units ?? 0;
82
+ const unitPrice = units > 0 ? ` ($${(price / units).toFixed(2)}/unit)` : "";
83
+ return ` ${pack.name ?? pack.key}: ${units} units for $${(pack.price_usd ?? "0.00")}${unitPrice}`;
84
+ }),
85
+ ];
86
+ }
70
87
  export function registerSolveTools(server) {
71
- server.tool("solve", "Solve a task by finding the best agent, paying, and executing. The primary way to use the marketplace. If spending confirmation is enabled, returns a price quote first — call again with confirmed: true to execute. Local file paths in the input (e.g. /Users/.../photo.jpg) are automatically uploaded to temporary storage and replaced with download URLs — just pass the path directly, no base64 encoding needed.", {
88
+ server.tool("solve", "Solve a task by finding the best agent, paying, and executing. The primary way to use the marketplace. If spending confirmation is enabled, returns a price quote first — call again with confirmed: true to execute. Local file paths in the input are auto-uploaded before execution.", {
72
89
  intent: z
73
90
  .string()
74
91
  .describe("What you want to accomplish (natural language)"),
@@ -95,9 +112,8 @@ export function registerSolveTools(server) {
95
112
  }, async ({ intent, input, budget, pay_with, confirmed }) => {
96
113
  if (!hasWalletConfigured()) {
97
114
  try {
98
- const { qr, url } = await initiateCardSetup();
99
- const blocks = formatCardSetupBlocks(qr, url);
100
- return { content: blocks.map((t) => ({ type: "text", text: t })) };
115
+ const { url } = await getOrCreatePendingCardSetup();
116
+ return multiText(...formatCardSetupBlocks(url));
101
117
  }
102
118
  catch {
103
119
  return text("No payment method configured.\n\n" +
@@ -105,8 +121,15 @@ export function registerSolveTools(server) {
105
121
  "To use crypto: wallet_setup({ action: \"create\" })");
106
122
  }
107
123
  }
108
- const method = pay_with ?? getConfiguredMethods()[0];
109
- // Path 1: If authenticated, use the platform /solve route
124
+ const pendingKey = makeSolvePendingKey(intent, input, budget);
125
+ const pending = pendingSolves.get(pendingKey);
126
+ const configuredMethods = getConfiguredMethods();
127
+ const requestedMethod = pay_with ?? pending?.method;
128
+ const normalizedRequestedMethod = requestedMethod ? normalizePaymentMethod(requestedMethod) : null;
129
+ if (requestedMethod && !normalizedRequestedMethod) {
130
+ return text(`Payment method "${requestedMethod}" is not configured.\n\n` +
131
+ "Use wallet_status to review your current payment methods.");
132
+ }
110
133
  let processedInput;
111
134
  try {
112
135
  const uploadResult = await uploadLocalFiles(input);
@@ -129,8 +152,9 @@ export function registerSolveTools(server) {
129
152
  storeFeedbackToken(jobId, result.feedback_token, agentId);
130
153
  }
131
154
  const cost = result.cost;
155
+ const usedCreditPack = result.consumption_mode === "credit_pack";
132
156
  const tipMsg = result.feedback_token ? await autoTip(jobId, agentId, agentName, result.feedback_token) : "";
133
- return multiText(formatRunResult(result), feedbackAsk(jobId, agentId, cost, tipMsg));
157
+ return multiText(formatRunResult(result), feedbackAsk(jobId, agentId, usedCreditPack ? undefined : cost, tipMsg));
134
158
  }
135
159
  catch (err) {
136
160
  const isAuthError = err instanceof Error &&
@@ -139,9 +163,10 @@ export function registerSolveTools(server) {
139
163
  if (!isAuthError || !hasWalletConfigured())
140
164
  throw err;
141
165
  }
142
- // Path 2: Wallet-only discovery — find agents, pick best, pay via MPP
143
166
  const params = new URLSearchParams({ q: intent, limit: "5" });
144
- const acceptedMethods = getAcceptedPaymentMethods();
167
+ const acceptedMethods = requestedMethod
168
+ ? [toRegistryPaymentMethod(requestedMethod)].filter((value) => !!value)
169
+ : getAcceptedPaymentMethods();
145
170
  if (acceptedMethods.length > 0) {
146
171
  params.set("accepted_payment_methods", acceptedMethods.join(","));
147
172
  }
@@ -149,64 +174,93 @@ export function registerSolveTools(server) {
149
174
  if (!agents || agents.length === 0) {
150
175
  return text(`No agents found matching "${intent}".`);
151
176
  }
152
- // Show discovery results
153
177
  const discovery = agentList(agents, intent);
154
- // Pick cheapest agent within budget
155
178
  const inputTokens = Math.ceil(JSON.stringify(input).length / 4);
156
- const affordable = agents.filter((a) => {
157
- const price = parseFloat(a.pricePer1kTokens ?? "0.01");
158
- const cost = a.pricingModel === "fixed" ? price : (inputTokens / 1000) * price;
179
+ const affordable = agents.filter((agent) => {
180
+ const price = parseFloat(agent.pricePer1kTokens ?? "0.01");
181
+ const cost = agent.pricingModel === "fixed" ? price : (inputTokens / 1000) * price;
159
182
  return cost <= budget;
160
183
  });
161
184
  const selected = affordable[0] ?? agents[0];
185
+ const activeCreditPack = await getCreditPackInventory(selected.id).then(getActiveCreditPack);
186
+ const compatibleMethods = getCompatiblePaymentMethods(selected, configuredMethods);
187
+ if (!activeCreditPack && normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
188
+ return text(`The best matching agent cannot be paid with "${requestedMethod}".\n\n` +
189
+ `Available payment methods for ${selected.name}: ${compatibleMethods.join(", ") || "none"}.\n` +
190
+ "Choose another payment method or refine your search.");
191
+ }
192
+ if (!activeCreditPack && !requestedMethod && compatibleMethods.length === 0) {
193
+ return text(`No compatible payment methods are configured for ${selected.name}.\n\n` +
194
+ `Your configured methods: ${configuredMethods.join(", ") || "none"}\n` +
195
+ `Agent accepts: ${selected.payment?.accepted_payments?.join(", ") || "unknown"}\n` +
196
+ "Use wallet_status to review your current setup.");
197
+ }
198
+ if (!activeCreditPack && !requestedMethod && compatibleMethods.length > 1) {
199
+ return text([
200
+ discovery,
201
+ "",
202
+ formatPaymentChoicePrompt(selected.name ?? selected.id, compatibleMethods, compatibleMethods.map((method) => formatSolveConfirmationCommand(intent, budget, method).replace(", confirmed: true", ""))),
203
+ ].join("\n"));
204
+ }
205
+ const method = activeCreditPack
206
+ ? undefined
207
+ : resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
162
208
  const selectedPrice = parseFloat(selected.pricePer1kTokens ?? "0.01");
163
209
  const estimatedCost = selected.pricingModel === "fixed"
164
210
  ? selectedPrice
165
211
  : (inputTokens / 1000) * selectedPrice;
166
- // Confirmation step: show discovery + price, wait for confirmed: true
167
- if (requiresSpendConfirmation() && !confirmed) {
212
+ if (!activeCreditPack && requiresSpendConfirmation() && !confirmed) {
213
+ pendingSolves.set(pendingKey, { method });
168
214
  return text([
169
215
  discovery,
170
216
  "",
171
217
  `Best match: ${selected.name}`,
172
218
  `Cost: $${estimatedCost.toFixed(2)}`,
173
- `Payment: ${method}`,
219
+ `Payment: ${formatPaymentLabel(method)}`,
220
+ ...(() => {
221
+ const summary = buildCreditPackSummary(selected);
222
+ return summary.length > 0 ? ["", ...summary] : [];
223
+ })(),
174
224
  "",
175
225
  "To proceed, call:",
176
- ` solve({ intent: "${intent}", input: <same>, budget: ${budget}, confirmed: true })`,
226
+ formatSolveConfirmationCommand(intent, budget, method),
177
227
  "",
178
228
  "To cancel, do nothing.",
179
229
  ].join("\n"));
180
230
  }
181
231
  let result;
182
- let processedInput2;
183
232
  try {
184
- const uploadResult2 = await uploadLocalFiles(input);
185
- processedInput2 = uploadResult2.input;
186
- }
187
- catch (err) {
188
- const msg = err instanceof Error ? err.message : "File upload failed";
189
- return text(`Error: ${msg}`);
190
- }
191
- try {
192
- result = await apiPostWithPayment(`/agents/${selected.id}/run`, { input: processedInput2 }, method);
233
+ result = activeCreditPack
234
+ ? await apiPost(`/agents/${selected.id}/run`, { input: processedInput }, { ensureConsumerPrincipal: true })
235
+ : await apiPostWithPayment(`/agents/${selected.id}/run`, { input: processedInput }, method);
193
236
  }
194
237
  catch (err) {
195
238
  const apiErr = err;
239
+ if (activeCreditPack && apiErr?.status === 402) {
240
+ return text(`Your available credit packs for ${selected.name} could not cover this run.\n\n` +
241
+ "Use list_agent_credit_packs to inspect your balances, or retry with a payment method.");
242
+ }
196
243
  if (apiErr?.status === 402) {
197
- return text("Payment failed — your wallet may not have enough USDC.\n\n" +
198
- "Check your balance and fund your wallet, then try again.\n" +
199
- "Use wallet_status to check your current payment methods.");
244
+ return text([
245
+ "Payment failed — your wallet may not have enough funds or the selected method was rejected.",
246
+ "",
247
+ "Check your balance and try again.",
248
+ "Use wallet_status to check your current payment methods.",
249
+ ...(() => {
250
+ const summary = buildCreditPackSummary(selected);
251
+ return summary.length > 0 ? ["", ...summary] : [];
252
+ })(),
253
+ ].join("\n"));
200
254
  }
201
255
  return text(`Error: ${apiErr?.message ?? "Failed to run agent"}`);
202
256
  }
257
+ pendingSolves.delete(pendingKey);
203
258
  const jobId = result.job_id ?? "";
204
- const agentId2 = result.agent_id ?? selected.id;
259
+ const agentId = result.agent_id ?? selected.id;
205
260
  const status = result.status;
206
261
  if (result.feedback_token) {
207
- storeFeedbackToken(jobId, result.feedback_token, agentId2);
262
+ storeFeedbackToken(jobId, result.feedback_token, agentId);
208
263
  }
209
- // Async agent — poll until complete
210
264
  if (status === "processing") {
211
265
  const pollResult = await pollSolveJob(jobId);
212
266
  if (pollResult.status === "completed") {
@@ -224,9 +278,9 @@ export function registerSolveTools(server) {
224
278
  asyncFormatted,
225
279
  ].join("\n");
226
280
  const asyncCost = result.cost;
227
- return multiText(asyncOutput, feedbackAsk(jobId, agentId2, asyncCost, ""));
281
+ const usedCreditPack = result.consumption_mode === "credit_pack";
282
+ return multiText(asyncOutput, feedbackAsk(jobId, agentId, usedCreditPack ? undefined : asyncCost, ""));
228
283
  }
229
- // Async agent failed
230
284
  const failedFormatted = formatRunResult({
231
285
  ...result,
232
286
  status: "failed",
@@ -240,10 +294,14 @@ export function registerSolveTools(server) {
240
294
  "",
241
295
  failedFormatted,
242
296
  ].join("\n");
243
- return multiText(failedOutput, "The agent execution failed. A refund has been initiated automatically.");
297
+ const usedCreditPack = result.consumption_mode === "credit_pack";
298
+ return multiText(failedOutput, usedCreditPack
299
+ ? "The agent execution failed and the reserved credit-pack unit was released automatically."
300
+ : "The agent execution failed. A refund has been initiated automatically.");
244
301
  }
245
302
  const actualCost = result.cost;
246
- const tipMsg = result.feedback_token ? await autoTip(jobId, agentId2, selected.name ?? "", result.feedback_token) : "";
303
+ const usedCreditPack = result.consumption_mode === "credit_pack";
304
+ const tipMsg = result.feedback_token ? await autoTip(jobId, agentId, selected.name ?? "", result.feedback_token) : "";
247
305
  const output = [
248
306
  discovery,
249
307
  "",
@@ -252,6 +310,6 @@ export function registerSolveTools(server) {
252
310
  "",
253
311
  formatRunResult(result, { paymentMethod: method }),
254
312
  ].join("\n");
255
- return multiText(output, feedbackAsk(jobId, agentId2, actualCost, tipMsg));
313
+ return multiText(output, feedbackAsk(jobId, agentId, usedCreditPack ? undefined : actualCost, tipMsg));
256
314
  });
257
315
  }
@@ -1,30 +1,50 @@
1
1
  import { z } from "zod";
2
- import { getWallets, getCardConfig, setCardConfig, addWallet } from "../core/config.js";
2
+ import { getWallets, getCardConfig, setCardConfig, addWallet, getPendingCardSetupToken, } from "../core/config.js";
3
3
  import { getWalletAddress } from "../core/payments.js";
4
- import { initiateCardSetup, pollCardSetup } from "../core/card-setup.js";
5
- import { isOwsAvailable, createOwsWallet, importKeyToOws, listOwsWallets, } from "../core/ows-adapter.js";
4
+ import { getOrCreatePendingCardSetup, formatCardSetupBlocks, getCardCapabilities, pollCardSetup, } from "../core/card-setup.js";
5
+ import { isOwsAvailable, createOwsWallet, importKeyToOws, listOwsWallets, listOwsWalletsByChain, } from "../core/ows-adapter.js";
6
+ import { ensureConsumerPrincipal, getConsumerPrincipal } from "../core/principal.js";
6
7
  function text(t) {
7
8
  return { content: [{ type: "text", text: t }] };
8
9
  }
9
- // Pending card setup — stored between the QR display call and the confirmation poll
10
- let pendingCardSetup = null;
11
10
  export function registerWalletTools(server) {
12
11
  // ── wallet_status (extracted from check_wallet) ─────────────────
13
12
  server.tool("wallet_status", "Check payment readiness. Shows all configured wallets, their chains, addresses, and card status.", {}, async () => {
14
13
  const wallets = getWallets();
15
14
  const card = getCardConfig();
16
- if (wallets.length === 0 && !card) {
15
+ const pendingCardSetupToken = getPendingCardSetupToken();
16
+ const consumerPrincipal = await getConsumerPrincipal();
17
+ if (wallets.length === 0 && !card && !pendingCardSetupToken) {
17
18
  return text("No payment methods configured.\nUse wallet_setup to create or import a wallet.");
18
19
  }
19
20
  const lines = ["Payment methods:"];
20
21
  for (const w of wallets) {
21
- const addr = await getWalletAddress(w.id);
22
22
  const label = w.label ? ` (${w.label})` : "";
23
23
  const storage = w.keyType === "ows" ? " [encrypted]" : " [plaintext]";
24
- lines.push(` ${w.id}${label}${storage}: ${w.chains.join(", ")} \u2014 ${addr ?? "unknown"}`);
24
+ const chainAddresses = await Promise.all(w.chains.map(async (chainName) => {
25
+ const addr = await getWalletAddress(chainName);
26
+ return `${chainName}: ${addr ?? "unknown"}`;
27
+ }));
28
+ lines.push(` ${w.id}${label}${storage}: ${chainAddresses.join(" | ")}`);
25
29
  }
26
30
  if (card) {
27
31
  lines.push(` Card: ${card.brand} ****${card.last4}`);
32
+ const capabilities = await getCardCapabilities();
33
+ if (capabilities.spt_status === "enabled") {
34
+ lines.push(" Card MPP: ready");
35
+ }
36
+ else if (capabilities.spt_status === "unavailable") {
37
+ lines.push(` Card MPP: unavailable — ${capabilities.message ?? "Stripe Shared Payment Token access is not enabled."}`);
38
+ }
39
+ else {
40
+ lines.push(` Card MPP: unknown — ${capabilities.message ?? "Could not determine card payment readiness."}`);
41
+ }
42
+ }
43
+ if (pendingCardSetupToken) {
44
+ lines.push(" Card setup: pending confirmation");
45
+ }
46
+ if (consumerPrincipal) {
47
+ lines.push("", `Consumer principal: ${consumerPrincipal}`);
28
48
  }
29
49
  return text(lines.join("\n"));
30
50
  });
@@ -42,7 +62,7 @@ export function registerWalletTools(server) {
42
62
  .optional()
43
63
  .describe("Private key hex string (required for 'import', ignored for 'create')"),
44
64
  chain: z.enum(["tempo", "base", "solana"]).optional()
45
- .describe("Primary chain (default: tempo). Solana support is via Stripe deposit mode."),
65
+ .describe("Primary chain (default: tempo). Solana uses an OWS wallet plus Stripe deposit-mode USDC."),
46
66
  }, async ({ action, name, key, chain }) => {
47
67
  // ── Card setup flow ──────────────────────────────────────
48
68
  if (action === "add-card") {
@@ -50,48 +70,32 @@ export function registerWalletTools(server) {
50
70
  if (existing) {
51
71
  return text(`Card already connected: ${existing.brand} ****${existing.last4}\n\nTo replace it, remove the current card first.`);
52
72
  }
73
+ const pendingToken = getPendingCardSetupToken();
53
74
  // Check if there's a pending setup to complete (user said they're done)
54
- if (pendingCardSetup) {
55
- const pendingToken = pendingCardSetup.token;
56
- const result = await pollCardSetup(pendingToken, 120_000);
57
- pendingCardSetup = null;
75
+ if (pendingToken) {
76
+ const result = await pollCardSetup(pendingToken, 250);
58
77
  if (result) {
59
- return text(`Connected! ${result.brand} ****${result.last4} is ready for payments.`);
78
+ const principal = await ensureConsumerPrincipal();
79
+ return text(`Connected! ${result.brand} ****${result.last4} is ready for payments.\n\n` +
80
+ `Consumer principal: ${principal}`);
60
81
  }
61
- return text("Card setup timed out. Run wallet_setup({ action: \"add-card\" }) to try again.");
62
- }
63
- // Create new card setup session with QR code, then auto-poll
64
- try {
65
- const { qr, url, token } = await initiateCardSetup();
66
- // Auto-poll for up to 90 seconds — if the user enters their card
67
- // quickly, the tool returns success without a second call
68
- const result = await pollCardSetup(token, 90_000);
69
- if (result) {
82
+ try {
83
+ const { url } = await getOrCreatePendingCardSetup();
84
+ const blocks = formatCardSetupBlocks(url);
70
85
  return {
71
- content: [
72
- { type: "text", text: "\n" + qr.trim() },
73
- { type: "text", text: [
74
- `Open to connect a card: ${url}`,
75
- "",
76
- `Connected! ${result.brand} ****${result.last4} is ready for payments.`,
77
- ].join("\n") },
78
- ],
86
+ content: blocks.map((t) => ({ type: "text", text: t })),
79
87
  };
80
88
  }
81
- // Timed out — save token for manual follow-up
82
- pendingCardSetup = { token };
89
+ catch {
90
+ return text("Card setup timed out. Run wallet_setup({ action: \"add-card\" }) to try again.");
91
+ }
92
+ }
93
+ // Create new card setup session and return immediately so clients
94
+ // don't appear hung while the user is still in the browser flow.
95
+ try {
96
+ const { url } = await getOrCreatePendingCardSetup();
83
97
  return {
84
- content: [
85
- { type: "text", text: "\n" + qr.trim() },
86
- { type: "text", text: [
87
- `IMPORTANT: Present this link to the user to connect a payment card:`,
88
- "",
89
- url,
90
- "",
91
- `Tell the user: "Scan the QR code above or open this link to connect your card. Let me know when you're done."`,
92
- `After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
93
- ].join("\n") },
94
- ],
98
+ content: formatCardSetupBlocks(url).map((t) => ({ type: "text", text: t })),
95
99
  };
96
100
  }
97
101
  catch {
@@ -113,16 +117,20 @@ export function registerWalletTools(server) {
113
117
  }
114
118
  const selectedChains = chain === "solana" ? ["solana"] : ["tempo", "base"];
115
119
  const defaultCh = chain ?? "tempo";
120
+ const preferredOwsChain = defaultCh === "solana" ? "solana" : "evm";
116
121
  const owsReady = await isOwsAvailable();
117
122
  if (action === "create") {
118
123
  // Check for existing OWS wallets first
119
124
  if (owsReady) {
120
- const existing = await listOwsWallets();
125
+ const existing = preferredOwsChain === "solana"
126
+ ? await listOwsWalletsByChain("solana")
127
+ : await listOwsWallets();
121
128
  if (existing.length > 0) {
122
129
  const w = existing[0];
123
130
  // Check if already in config
124
131
  const wallets = getWallets();
125
- const alreadyLinked = wallets.find(wl => wl.owsWalletId === w.id);
132
+ const alreadyLinked = wallets.find((wl) => wl.owsWalletId === w.id);
133
+ let chainStatus = "Linked to Agent Wonderland.";
126
134
  if (!alreadyLinked) {
127
135
  addWallet({
128
136
  id: w.name,
@@ -133,14 +141,29 @@ export function registerWalletTools(server) {
133
141
  label: w.name,
134
142
  });
135
143
  }
144
+ else {
145
+ const mergedChains = [...new Set([...alreadyLinked.chains, ...selectedChains])];
146
+ if (mergedChains.length !== alreadyLinked.chains.length) {
147
+ addWallet({
148
+ ...alreadyLinked,
149
+ chains: mergedChains,
150
+ });
151
+ chainStatus = `Updated linked chains: ${mergedChains.join(", ")}.`;
152
+ }
153
+ else {
154
+ chainStatus = "Already linked to Agent Wonderland.";
155
+ }
156
+ }
157
+ const principal = await ensureConsumerPrincipal();
136
158
  return text([
137
159
  "Found existing OWS wallet:",
138
160
  ` Name: ${w.name}`,
139
161
  ` Address: ${w.address}`,
140
162
  ` Chains: ${selectedChains.join(", ")}`,
141
163
  ` Storage: ~/.ows/ (encrypted)`,
164
+ ` Consumer principal: ${principal}`,
142
165
  "",
143
- alreadyLinked ? "Already linked to Agent Wonderland." : "Linked to Agent Wonderland.",
166
+ chainStatus,
144
167
  "",
145
168
  `Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
146
169
  ].join("\n"));
@@ -149,10 +172,12 @@ export function registerWalletTools(server) {
149
172
  if (!owsReady) {
150
173
  return text("Cannot create wallet: OWS (Open Wallet Standard) is not installed.\n" +
151
174
  "Install with: npm install -g @open-wallet-standard/core\n\n" +
152
- "Alternatively, use action 'import' with an existing private key.");
175
+ (defaultCh === "solana"
176
+ ? "Solana wallets currently require OWS-managed encrypted storage."
177
+ : "Alternatively, use action 'import' with an existing private key."));
153
178
  }
154
179
  const walletName = name ?? `aw-${Date.now()}`;
155
- const result = await createOwsWallet(walletName);
180
+ const result = await createOwsWallet(walletName, preferredOwsChain);
156
181
  // Persist to config so payment flows can find it
157
182
  addWallet({
158
183
  id: walletName,
@@ -162,6 +187,7 @@ export function registerWalletTools(server) {
162
187
  defaultChain: defaultCh,
163
188
  label: walletName,
164
189
  });
190
+ const principal = await ensureConsumerPrincipal();
165
191
  return text([
166
192
  `Wallet created [encrypted]:`,
167
193
  ` ID: ${result.walletId}`,
@@ -169,6 +195,7 @@ export function registerWalletTools(server) {
169
195
  ` Name: ${walletName}`,
170
196
  ` Chains: ${selectedChains.join(", ")}`,
171
197
  ` Storage: ~/.ows/ (AES-256-GCM encrypted)`,
198
+ ` Consumer principal: ${principal}`,
172
199
  "",
173
200
  `Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
174
201
  "For testnet: npx mppx account fund",
@@ -177,7 +204,7 @@ export function registerWalletTools(server) {
177
204
  // action === "import"
178
205
  if (owsReady) {
179
206
  const walletName = name ?? `imported-${Date.now()}`;
180
- const result = await importKeyToOws(key, walletName);
207
+ const result = await importKeyToOws(key, walletName, preferredOwsChain);
181
208
  addWallet({
182
209
  id: walletName,
183
210
  keyType: "ows",
@@ -186,14 +213,20 @@ export function registerWalletTools(server) {
186
213
  defaultChain: defaultCh,
187
214
  label: walletName,
188
215
  });
216
+ const principal = await ensureConsumerPrincipal();
189
217
  return text([
190
218
  `Key imported to OWS [encrypted]:`,
191
219
  ` ID: ${result.walletId}`,
192
220
  ` Address: ${result.address}`,
193
221
  ` Name: ${walletName}`,
194
222
  ` Chains: ${selectedChains.join(", ")}`,
223
+ ` Consumer principal: ${principal}`,
195
224
  ].join("\n"));
196
225
  }
226
+ if (defaultCh === "solana") {
227
+ return text("Solana key import currently requires OWS (Open Wallet Standard) so the ed25519 key can be stored safely.\n" +
228
+ "Install with: npm install -g @open-wallet-standard/core");
229
+ }
197
230
  // No OWS — store plaintext
198
231
  try {
199
232
  const { privateKeyToAccount } = await import("viem/accounts");
@@ -208,11 +241,13 @@ export function registerWalletTools(server) {
208
241
  defaultChain: defaultCh,
209
242
  label: walletName,
210
243
  });
244
+ const principal = await ensureConsumerPrincipal();
211
245
  return text([
212
246
  `Key imported [plaintext] \u2014 OWS not available for encrypted storage.`,
213
247
  ` Address: ${account.address}`,
214
248
  ` Name: ${walletName}`,
215
249
  ` Chains: ${selectedChains.join(", ")}`,
250
+ ` Consumer principal: ${principal}`,
216
251
  "",
217
252
  "Install OWS for encrypted storage: npm install -g @open-wallet-standard/core",
218
253
  ].join("\n"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentwonderland/mcp",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "type": "module",
5
5
  "description": "MCP server for the Agent Wonderland AI agent marketplace",
6
6
  "bin": {
@@ -23,6 +23,8 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@modelcontextprotocol/sdk": "^1.12.1",
26
+ "@solana/spl-token": "^0.4.14",
27
+ "@solana/web3.js": "^1.98.4",
26
28
  "mppx": "^0.4.9",
27
29
  "qrcode": "^1.5.4",
28
30
  "viem": "^2.47.6",