@agentwonderland/mcp 0.1.22 → 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 +87 -30
  15. package/dist/core/config.d.ts +4 -0
  16. package/dist/core/config.js +28 -1
  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 +121 -16
  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 +13 -4
  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 +116 -49
  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 +112 -35
  55. package/src/core/config.ts +33 -2
  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 +140 -15
  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 +13 -4
  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 +174 -53
  73. package/src/tools/solve.ts +149 -56
  74. package/src/tools/wallet.ts +102 -52
@@ -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";
13
16
  import { uploadLocalFiles } from "../core/file-upload.js";
14
17
  import type { AgentRecord } from "../core/types.js";
15
18
  import { storeFeedbackToken } from "./_token-cache.js";
19
+ import {
20
+ formatPaymentChoicePrompt,
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()
@@ -110,9 +138,7 @@ export function registerSolveTools(server: McpServer): void {
110
138
  pay_with: z
111
139
  .string()
112
140
  .optional()
113
- .describe(
114
- "Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted.",
115
- ),
141
+ .describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
116
142
  confirmed: z
117
143
  .boolean()
118
144
  .optional()
@@ -121,21 +147,30 @@ export function registerSolveTools(server: McpServer): void {
121
147
  async ({ intent, input, budget, pay_with, confirmed }) => {
122
148
  if (!hasWalletConfigured()) {
123
149
  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 })) };
150
+ const { url } = await getOrCreatePendingCardSetup();
151
+ return multiText(...formatCardSetupBlocks(url));
127
152
  } catch {
128
153
  return text(
129
154
  "No payment method configured.\n\n" +
130
155
  "To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
131
- "To use crypto: wallet_setup({ action: \"create\" })"
156
+ "To use crypto: wallet_setup({ action: \"create\" })",
132
157
  );
133
158
  }
134
159
  }
135
160
 
136
- const method = pay_with ?? getConfiguredMethods()[0];
161
+ const pendingKey = makeSolvePendingKey(intent, input, budget);
162
+ const pending = pendingSolves.get(pendingKey);
163
+ const configuredMethods = getConfiguredMethods();
164
+ const requestedMethod = pay_with ?? pending?.method;
165
+ const normalizedRequestedMethod = requestedMethod ? normalizePaymentMethod(requestedMethod) : null;
166
+
167
+ if (requestedMethod && !normalizedRequestedMethod) {
168
+ return text(
169
+ `Payment method "${requestedMethod}" is not configured.\n\n` +
170
+ "Use wallet_status to review your current payment methods.",
171
+ );
172
+ }
137
173
 
138
- // Path 1: If authenticated, use the platform /solve route
139
174
  let processedInput: Record<string, unknown>;
140
175
  try {
141
176
  const uploadResult = await uploadLocalFiles(input);
@@ -144,6 +179,7 @@ export function registerSolveTools(server: McpServer): void {
144
179
  const msg = err instanceof Error ? err.message : "File upload failed";
145
180
  return text(`Error: ${msg}`);
146
181
  }
182
+
147
183
  try {
148
184
  const result = await apiPost<Record<string, unknown>>("/solve", {
149
185
  intent,
@@ -159,8 +195,9 @@ export function registerSolveTools(server: McpServer): void {
159
195
  }
160
196
 
161
197
  const cost = (result as Record<string, unknown>).cost as number | undefined;
198
+ const usedCreditPack = (result as Record<string, unknown>).consumption_mode === "credit_pack";
162
199
  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));
200
+ return multiText(formatRunResult(result), feedbackAsk(jobId, agentId, usedCreditPack ? undefined : cost, tipMsg));
164
201
  } catch (err: unknown) {
165
202
  const isAuthError =
166
203
  err instanceof Error &&
@@ -169,9 +206,10 @@ export function registerSolveTools(server: McpServer): void {
169
206
  if (!isAuthError || !hasWalletConfigured()) throw err;
170
207
  }
171
208
 
172
- // Path 2: Wallet-only discovery — find agents, pick best, pay via MPP
173
209
  const params = new URLSearchParams({ q: intent, limit: "5" });
174
- const acceptedMethods = getAcceptedPaymentMethods();
210
+ const acceptedMethods = requestedMethod
211
+ ? [toRegistryPaymentMethod(requestedMethod)].filter((value): value is string => !!value)
212
+ : getAcceptedPaymentMethods();
175
213
  if (acceptedMethods.length > 0) {
176
214
  params.set("accepted_payment_methods", acceptedMethods.join(","));
177
215
  }
@@ -181,77 +219,125 @@ export function registerSolveTools(server: McpServer): void {
181
219
  return text(`No agents found matching "${intent}".`);
182
220
  }
183
221
 
184
- // Show discovery results
185
222
  const discovery = agentList(agents, intent);
186
-
187
- // Pick cheapest agent within budget
188
223
  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;
224
+ const affordable = agents.filter((agent) => {
225
+ const price = parseFloat(agent.pricePer1kTokens ?? "0.01");
226
+ const cost = agent.pricingModel === "fixed" ? price : (inputTokens / 1000) * price;
193
227
  return cost <= budget;
194
228
  });
195
229
  const selected = affordable[0] ?? agents[0];
230
+ const activeCreditPack = await getCreditPackInventory(selected.id).then(getActiveCreditPack);
231
+ const compatibleMethods = getCompatiblePaymentMethods(selected, configuredMethods);
232
+
233
+ if (!activeCreditPack && normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
234
+ return text(
235
+ `The best matching agent cannot be paid with "${requestedMethod}".\n\n` +
236
+ `Available payment methods for ${selected.name}: ${compatibleMethods.join(", ") || "none"}.\n` +
237
+ "Choose another payment method or refine your search.",
238
+ );
239
+ }
240
+
241
+ if (!activeCreditPack && !requestedMethod && compatibleMethods.length === 0) {
242
+ return text(
243
+ `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
+ );
248
+ }
249
+
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);
196
267
 
197
268
  const selectedPrice = parseFloat(selected.pricePer1kTokens ?? "0.01");
198
- const estimatedCost =
199
- selected.pricingModel === "fixed"
200
- ? selectedPrice
201
- : (inputTokens / 1000) * selectedPrice;
269
+ const estimatedCost = selected.pricingModel === "fixed"
270
+ ? selectedPrice
271
+ : (inputTokens / 1000) * selectedPrice;
202
272
 
203
- // Confirmation step: show discovery + price, wait for confirmed: true
204
- if (requiresSpendConfirmation() && !confirmed) {
273
+ if (!activeCreditPack && requiresSpendConfirmation() && !confirmed) {
274
+ pendingSolves.set(pendingKey, { method });
205
275
  return text([
206
276
  discovery,
207
277
  "",
208
278
  `Best match: ${selected.name}`,
209
279
  `Cost: $${estimatedCost.toFixed(2)}`,
210
- `Payment: ${method}`,
280
+ `Payment: ${formatPaymentLabel(method)}`,
281
+ ...(() => {
282
+ const summary = buildCreditPackSummary(selected);
283
+ return summary.length > 0 ? ["", ...summary] : [];
284
+ })(),
211
285
  "",
212
286
  "To proceed, call:",
213
- ` solve({ intent: "${intent}", input: <same>, budget: ${budget}, confirmed: true })`,
287
+ formatSolveConfirmationCommand(intent, budget, method),
214
288
  "",
215
289
  "To cancel, do nothing.",
216
290
  ].join("\n"));
217
291
  }
218
292
 
219
293
  let result: Record<string, unknown>;
220
- let processedInput2: Record<string, unknown>;
221
294
  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}`);
227
- }
228
- try {
229
- result = await apiPostWithPayment<Record<string, unknown>>(
230
- `/agents/${selected.id}/run`,
231
- { input: processedInput2 },
232
- method,
233
- );
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>>(
302
+ `/agents/${selected.id}/run`,
303
+ { input: processedInput },
304
+ method,
305
+ );
234
306
  } catch (err: unknown) {
235
307
  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
+ }
236
314
  if (apiErr?.status === 402) {
237
315
  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."
316
+ [
317
+ "Payment failed — your wallet may not have enough funds or the selected method was rejected.",
318
+ "",
319
+ "Check your balance and try again.",
320
+ "Use wallet_status to check your current payment methods.",
321
+ ...(() => {
322
+ const summary = buildCreditPackSummary(selected);
323
+ return summary.length > 0 ? ["", ...summary] : [];
324
+ })(),
325
+ ].join("\n"),
241
326
  );
242
327
  }
243
328
  return text(`Error: ${apiErr?.message ?? "Failed to run agent"}`);
244
329
  }
245
330
 
331
+ pendingSolves.delete(pendingKey);
332
+
246
333
  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;
334
+ const agentId = (result as Record<string, unknown>).agent_id as string ?? selected.id;
248
335
  const status = result.status as string;
249
336
 
250
337
  if (result.feedback_token) {
251
- storeFeedbackToken(jobId, result.feedback_token as string, agentId2);
338
+ storeFeedbackToken(jobId, result.feedback_token as string, agentId);
252
339
  }
253
340
 
254
- // Async agent — poll until complete
255
341
  if (status === "processing") {
256
342
  const pollResult = await pollSolveJob(jobId);
257
343
  if (pollResult.status === "completed") {
@@ -271,10 +357,10 @@ export function registerSolveTools(server: McpServer): void {
271
357
  ].join("\n");
272
358
 
273
359
  const asyncCost = (result as Record<string, unknown>).cost as number | undefined;
274
- return multiText(asyncOutput, feedbackAsk(jobId, agentId2, asyncCost, ""));
360
+ const usedCreditPack = (result as Record<string, unknown>).consumption_mode === "credit_pack";
361
+ return multiText(asyncOutput, feedbackAsk(jobId, agentId, usedCreditPack ? undefined : asyncCost, ""));
275
362
  }
276
363
 
277
- // Async agent failed
278
364
  const failedFormatted = formatRunResult({
279
365
  ...result,
280
366
  status: "failed",
@@ -290,11 +376,18 @@ export function registerSolveTools(server: McpServer): void {
290
376
  failedFormatted,
291
377
  ].join("\n");
292
378
 
293
- return multiText(failedOutput, "The agent execution failed. A refund has been initiated automatically.");
379
+ const usedCreditPack = (result as Record<string, unknown>).consumption_mode === "credit_pack";
380
+ return multiText(
381
+ failedOutput,
382
+ usedCreditPack
383
+ ? "The agent execution failed and the reserved credit-pack unit was released automatically."
384
+ : "The agent execution failed. A refund has been initiated automatically.",
385
+ );
294
386
  }
295
387
 
296
388
  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) : "";
389
+ const usedCreditPack = (result as Record<string, unknown>).consumption_mode === "credit_pack";
390
+ const tipMsg = result.feedback_token ? await autoTip(jobId, agentId, selected.name ?? "", result.feedback_token as string) : "";
298
391
 
299
392
  const output = [
300
393
  discovery,
@@ -305,7 +398,7 @@ export function registerSolveTools(server: McpServer): void {
305
398
  formatRunResult(result, { paymentMethod: method }),
306
399
  ].join("\n");
307
400
 
308
- return multiText(output, feedbackAsk(jobId, agentId2, actualCost, tipMsg));
401
+ return multiText(output, feedbackAsk(jobId, agentId, usedCreditPack ? undefined : actualCost, tipMsg));
309
402
  },
310
403
  );
311
404
  }