@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,18 +1,29 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
  import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
4
- import { initiateCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
4
+ import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
5
5
  import {
6
+ getCompatiblePaymentMethods,
6
7
  hasWalletConfigured,
7
8
  getConfiguredMethods,
8
9
  getAcceptedPaymentMethods,
9
10
  getWalletAddress,
11
+ normalizePaymentMethod,
12
+ toRegistryPaymentMethod,
10
13
  } from "../core/payments.js";
11
14
  import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
12
15
  import { agentList, formatRunResult } from "../core/formatters.js";
16
+ import { canSpend, recordSpend, requiresPolicyConfirmation } from "../core/spend-policy.js";
13
17
  import { uploadLocalFiles } from "../core/file-upload.js";
14
18
  import type { AgentRecord } from "../core/types.js";
15
19
  import { storeFeedbackToken } from "./_token-cache.js";
20
+ import {
21
+ formatPaymentLabel,
22
+ formatSolveConfirmationCommand,
23
+ makeSolvePendingKey,
24
+ resolveConfirmationMethod,
25
+ } from "./_payment-confirmation.js";
26
+ import { getActiveCreditPack, getCreditPackInventory, getCreditPackProgram } from "../core/passes.js";
16
27
 
17
28
  const POLL_INTERVAL_MS = 3000;
18
29
  const POLL_MAX_MS = 120000;
@@ -40,7 +51,7 @@ async function pollSolveJob(
40
51
  return { status: "failed", output: job.output, error_code: job.error_code };
41
52
  }
42
53
  } catch {
43
- // Ignore poll errors, keep trying
54
+ // Ignore poll errors and keep trying.
44
55
  }
45
56
  }
46
57
 
@@ -55,11 +66,13 @@ function multiText(...blocks: string[]) {
55
66
  return { content: blocks.map((t) => ({ type: "text" as const, text: t })) };
56
67
  }
57
68
 
69
+ const pendingSolves = new Map<string, { method?: string }>();
70
+
58
71
  function feedbackAsk(jobId: string, agentId: string, cost?: number, tipSent?: string): string {
59
72
  const defaultTip = getDefaultTipAmount();
60
- const costLine = cost != null ? `Paid $${cost.toFixed(cost < 0.01 ? 4 : 2)}.` : "";
73
+ const costLine = cost != null ? `Paid $${cost.toFixed(cost < 0.01 ? 4 : 2)}.` : "This run used your available credit pack.";
61
74
  return [
62
- `IMPORTANT: After presenting the result above, you MUST ask the user:`,
75
+ "IMPORTANT: After presenting the result above, you MUST ask the user:",
63
76
  `"${costLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
64
77
  tipSent ?? "",
65
78
  "",
@@ -87,10 +100,25 @@ async function autoTip(jobId: string, agentId: string, agentName: string, feedba
87
100
  }
88
101
  }
89
102
 
103
+ function buildCreditPackSummary(agent: AgentRecord): string[] {
104
+ const creditPacks = getCreditPackProgram(agent);
105
+ if (!creditPacks?.packs?.length) return [];
106
+
107
+ return [
108
+ "Discounted credit packs available:",
109
+ ...creditPacks.packs.map((pack) => {
110
+ const price = Number(pack.price_usd ?? "0");
111
+ const units = pack.included_units ?? 0;
112
+ const unitPrice = units > 0 ? ` ($${(price / units).toFixed(2)}/unit)` : "";
113
+ return ` ${pack.name ?? pack.key}: ${units} units for $${(pack.price_usd ?? "0.00")}${unitPrice}`;
114
+ }),
115
+ ];
116
+ }
117
+
90
118
  export function registerSolveTools(server: McpServer): void {
91
119
  server.tool(
92
120
  "solve",
93
- "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.",
121
+ "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.",
94
122
  {
95
123
  intent: z
96
124
  .string()
@@ -109,10 +137,9 @@ export function registerSolveTools(server: McpServer): void {
109
137
  .describe("Maximum budget in USD"),
110
138
  pay_with: z
111
139
  .string()
112
- .optional()
113
- .describe(
114
- "Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted.",
115
- ),
140
+ .trim()
141
+ .min(1)
142
+ .describe("Required payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Use wallet_status to see configured options."),
116
143
  confirmed: z
117
144
  .boolean()
118
145
  .optional()
@@ -121,21 +148,30 @@ export function registerSolveTools(server: McpServer): void {
121
148
  async ({ intent, input, budget, pay_with, confirmed }) => {
122
149
  if (!hasWalletConfigured()) {
123
150
  try {
124
- const { qr, url } = await initiateCardSetup();
125
- const blocks = formatCardSetupBlocks(qr, url);
126
- return { content: blocks.map((t) => ({ type: "text" as const, text: t })) };
151
+ const { url } = await getOrCreatePendingCardSetup();
152
+ return multiText(...formatCardSetupBlocks(url));
127
153
  } catch {
128
154
  return text(
129
155
  "No payment method configured.\n\n" +
130
156
  "To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
131
- "To use crypto: wallet_setup({ action: \"create\" })"
157
+ "To use crypto: wallet_setup({ action: \"create\" })",
132
158
  );
133
159
  }
134
160
  }
135
161
 
136
- const method = pay_with ?? getConfiguredMethods()[0];
162
+ const pendingKey = makeSolvePendingKey(intent, input, budget);
163
+ const pending = pendingSolves.get(pendingKey);
164
+ const configuredMethods = getConfiguredMethods();
165
+ const requestedMethod = pay_with;
166
+ const normalizedRequestedMethod = normalizePaymentMethod(requestedMethod);
167
+
168
+ if (!normalizedRequestedMethod) {
169
+ return text(
170
+ `Payment method "${requestedMethod}" is not configured.\n\n` +
171
+ "Use wallet_status to review your current payment methods.",
172
+ );
173
+ }
137
174
 
138
- // Path 1: If authenticated, use the platform /solve route
139
175
  let processedInput: Record<string, unknown>;
140
176
  try {
141
177
  const uploadResult = await uploadLocalFiles(input);
@@ -144,6 +180,7 @@ export function registerSolveTools(server: McpServer): void {
144
180
  const msg = err instanceof Error ? err.message : "File upload failed";
145
181
  return text(`Error: ${msg}`);
146
182
  }
183
+
147
184
  try {
148
185
  const result = await apiPost<Record<string, unknown>>("/solve", {
149
186
  intent,
@@ -159,8 +196,9 @@ export function registerSolveTools(server: McpServer): void {
159
196
  }
160
197
 
161
198
  const cost = (result as Record<string, unknown>).cost as number | undefined;
199
+ const usedCreditPack = (result as Record<string, unknown>).consumption_mode === "credit_pack";
162
200
  const tipMsg = result.feedback_token ? await autoTip(jobId, agentId, agentName, result.feedback_token as string) : "";
163
- return multiText(formatRunResult(result), feedbackAsk(jobId, agentId, cost, tipMsg));
201
+ return multiText(formatRunResult(result), feedbackAsk(jobId, agentId, usedCreditPack ? undefined : cost, tipMsg));
164
202
  } catch (err: unknown) {
165
203
  const isAuthError =
166
204
  err instanceof Error &&
@@ -169,9 +207,10 @@ export function registerSolveTools(server: McpServer): void {
169
207
  if (!isAuthError || !hasWalletConfigured()) throw err;
170
208
  }
171
209
 
172
- // Path 2: Wallet-only discovery — find agents, pick best, pay via MPP
173
210
  const params = new URLSearchParams({ q: intent, limit: "5" });
174
- const acceptedMethods = getAcceptedPaymentMethods();
211
+ const acceptedMethods = requestedMethod
212
+ ? [toRegistryPaymentMethod(requestedMethod)].filter((value): value is string => !!value)
213
+ : getAcceptedPaymentMethods();
175
214
  if (acceptedMethods.length > 0) {
176
215
  params.set("accepted_payment_methods", acceptedMethods.join(","));
177
216
  }
@@ -181,77 +220,119 @@ export function registerSolveTools(server: McpServer): void {
181
220
  return text(`No agents found matching "${intent}".`);
182
221
  }
183
222
 
184
- // Show discovery results
185
223
  const discovery = agentList(agents, intent);
186
-
187
- // Pick cheapest agent within budget
188
- const inputTokens = Math.ceil(JSON.stringify(input).length / 4);
189
- const affordable = agents.filter((a) => {
190
- const price = parseFloat(a.pricePer1kTokens ?? "0.01");
191
- const cost =
192
- a.pricingModel === "fixed" ? price : (inputTokens / 1000) * price;
193
- return cost <= budget;
224
+ const affordable = agents.filter((agent) => {
225
+ const price = parseFloat(agent.pricePerRunUsd ?? "0.01");
226
+ return price <= budget;
194
227
  });
195
228
  const selected = affordable[0] ?? agents[0];
229
+ const activeCreditPack = await getCreditPackInventory(selected.id).then(getActiveCreditPack);
230
+ const compatibleMethods = getCompatiblePaymentMethods(selected, configuredMethods);
231
+
232
+ if (!compatibleMethods.includes(normalizedRequestedMethod)) {
233
+ return text(
234
+ `The best matching agent cannot be paid with "${requestedMethod}".\n\n` +
235
+ `Available payment methods for ${selected.name}: ${compatibleMethods.join(", ") || "none"}.\n` +
236
+ "Choose another payment method or refine your search.",
237
+ );
238
+ }
196
239
 
197
- const selectedPrice = parseFloat(selected.pricePer1kTokens ?? "0.01");
198
- const estimatedCost =
199
- selected.pricingModel === "fixed"
200
- ? selectedPrice
201
- : (inputTokens / 1000) * selectedPrice;
240
+ const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
241
+ const spendCheckMethod = method ?? normalizedRequestedMethod;
202
242
 
203
- // Confirmation step: show discovery + price, wait for confirmed: true
204
- if (requiresSpendConfirmation() && !confirmed) {
243
+ const selectedPrice = parseFloat(selected.pricePerRunUsd ?? "0.01");
244
+ const estimatedCost = selectedPrice;
245
+
246
+ const needsPolicyConfirmation = !activeCreditPack && requiresPolicyConfirmation(spendCheckMethod, estimatedCost);
247
+ if (!activeCreditPack && (requiresSpendConfirmation() || needsPolicyConfirmation) && !confirmed) {
248
+ pendingSolves.set(pendingKey, { method });
205
249
  return text([
206
250
  discovery,
207
251
  "",
208
252
  `Best match: ${selected.name}`,
209
253
  `Cost: $${estimatedCost.toFixed(2)}`,
210
- `Payment: ${method}`,
254
+ `Payment: ${formatPaymentLabel(method)}`,
255
+ ...(() => {
256
+ const summary = buildCreditPackSummary(selected);
257
+ return summary.length > 0 ? ["", ...summary] : [];
258
+ })(),
211
259
  "",
212
260
  "To proceed, call:",
213
- ` solve({ intent: "${intent}", input: <same>, budget: ${budget}, confirmed: true })`,
261
+ formatSolveConfirmationCommand(intent, budget, method),
214
262
  "",
215
263
  "To cancel, do nothing.",
216
264
  ].join("\n"));
217
265
  }
218
266
 
219
267
  let result: Record<string, unknown>;
220
- let processedInput2: Record<string, unknown>;
221
- try {
222
- const uploadResult2 = await uploadLocalFiles(input);
223
- processedInput2 = uploadResult2.input;
224
- } catch (err) {
225
- const msg = err instanceof Error ? err.message : "File upload failed";
226
- return text(`Error: ${msg}`);
268
+ if (!activeCreditPack) {
269
+ const spendCheck = canSpend({
270
+ method: spendCheckMethod,
271
+ amountUsd: estimatedCost,
272
+ });
273
+ if (!spendCheck.ok) {
274
+ return text(spendCheck.message);
275
+ }
227
276
  }
277
+
228
278
  try {
229
- result = await apiPostWithPayment<Record<string, unknown>>(
230
- `/agents/${selected.id}/run`,
231
- { input: processedInput2 },
232
- method,
233
- );
279
+ let usedPaidMethod = !activeCreditPack;
280
+ if (activeCreditPack) {
281
+ try {
282
+ result = await apiPost<Record<string, unknown>>(
283
+ `/agents/${selected.id}/run`,
284
+ { input: processedInput },
285
+ { ensureConsumerPrincipal: true },
286
+ );
287
+ } catch (packErr) {
288
+ const packApiErr = packErr as { status?: number };
289
+ if (packApiErr?.status !== 402) throw packErr;
290
+ result = await apiPostWithPayment<Record<string, unknown>>(
291
+ `/agents/${selected.id}/run`,
292
+ { input: processedInput },
293
+ method,
294
+ );
295
+ usedPaidMethod = true;
296
+ }
297
+ } else {
298
+ result = await apiPostWithPayment<Record<string, unknown>>(
299
+ `/agents/${selected.id}/run`,
300
+ { input: processedInput },
301
+ method,
302
+ );
303
+ }
304
+ if (usedPaidMethod) {
305
+ recordSpend(spendCheckMethod, estimatedCost);
306
+ }
234
307
  } catch (err: unknown) {
235
308
  const apiErr = err as { status?: number; message?: string };
236
309
  if (apiErr?.status === 402) {
237
310
  return text(
238
- "Payment failed — your wallet may not have enough USDC.\n\n" +
239
- "Check your balance and fund your wallet, then try again.\n" +
240
- "Use wallet_status to check your current payment methods."
311
+ [
312
+ "Payment failed — your wallet may not have enough funds or the selected method was rejected.",
313
+ "",
314
+ "Check your balance and try again.",
315
+ "Use wallet_status to check your current payment methods.",
316
+ ...(() => {
317
+ const summary = buildCreditPackSummary(selected);
318
+ return summary.length > 0 ? ["", ...summary] : [];
319
+ })(),
320
+ ].join("\n"),
241
321
  );
242
322
  }
243
323
  return text(`Error: ${apiErr?.message ?? "Failed to run agent"}`);
244
324
  }
245
325
 
326
+ pendingSolves.delete(pendingKey);
327
+
246
328
  const jobId = (result as Record<string, unknown>).job_id as string ?? "";
247
- const agentId2 = (result as Record<string, unknown>).agent_id as string ?? selected.id;
329
+ const agentId = (result as Record<string, unknown>).agent_id as string ?? selected.id;
248
330
  const status = result.status as string;
249
331
 
250
332
  if (result.feedback_token) {
251
- storeFeedbackToken(jobId, result.feedback_token as string, agentId2);
333
+ storeFeedbackToken(jobId, result.feedback_token as string, agentId);
252
334
  }
253
335
 
254
- // Async agent — poll until complete
255
336
  if (status === "processing") {
256
337
  const pollResult = await pollSolveJob(jobId);
257
338
  if (pollResult.status === "completed") {
@@ -271,10 +352,10 @@ export function registerSolveTools(server: McpServer): void {
271
352
  ].join("\n");
272
353
 
273
354
  const asyncCost = (result as Record<string, unknown>).cost as number | undefined;
274
- return multiText(asyncOutput, feedbackAsk(jobId, agentId2, asyncCost, ""));
355
+ const usedCreditPack = (result as Record<string, unknown>).consumption_mode === "credit_pack";
356
+ return multiText(asyncOutput, feedbackAsk(jobId, agentId, usedCreditPack ? undefined : asyncCost, ""));
275
357
  }
276
358
 
277
- // Async agent failed
278
359
  const failedFormatted = formatRunResult({
279
360
  ...result,
280
361
  status: "failed",
@@ -290,11 +371,18 @@ export function registerSolveTools(server: McpServer): void {
290
371
  failedFormatted,
291
372
  ].join("\n");
292
373
 
293
- return multiText(failedOutput, "The agent execution failed. A refund has been initiated automatically.");
374
+ const usedCreditPack = (result as Record<string, unknown>).consumption_mode === "credit_pack";
375
+ return multiText(
376
+ failedOutput,
377
+ usedCreditPack
378
+ ? "The agent execution failed and the reserved credit-pack unit was released automatically."
379
+ : "The agent execution failed. A refund has been initiated automatically.",
380
+ );
294
381
  }
295
382
 
296
383
  const actualCost = (result as Record<string, unknown>).cost as number | undefined;
297
- const tipMsg = result.feedback_token ? await autoTip(jobId, agentId2, selected.name ?? "", result.feedback_token as string) : "";
384
+ const usedCreditPack = (result as Record<string, unknown>).consumption_mode === "credit_pack";
385
+ const tipMsg = result.feedback_token ? await autoTip(jobId, agentId, selected.name ?? "", result.feedback_token as string) : "";
298
386
 
299
387
  const output = [
300
388
  discovery,
@@ -305,7 +393,7 @@ export function registerSolveTools(server: McpServer): void {
305
393
  formatRunResult(result, { paymentMethod: method }),
306
394
  ].join("\n");
307
395
 
308
- return multiText(output, feedbackAsk(jobId, agentId2, actualCost, tipMsg));
396
+ return multiText(output, feedbackAsk(jobId, agentId, usedCreditPack ? undefined : actualCost, tipMsg));
309
397
  },
310
398
  );
311
399
  }