@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
package/src/tools/run.ts CHANGED
@@ -2,18 +2,36 @@ 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
4
  import { uploadLocalFiles } from "../core/file-upload.js";
5
- import { getConfiguredMethods, hasWalletConfigured, getWalletAddress } from "../core/payments.js";
5
+ import {
6
+ formatCreditPackOffer,
7
+ getActiveCreditPack,
8
+ getCreditPackInventory,
9
+ getCreditPackProgram,
10
+ } from "../core/passes.js";
11
+ import {
12
+ getCompatiblePaymentMethods,
13
+ getConfiguredMethods,
14
+ hasWalletConfigured,
15
+ getWalletAddress,
16
+ normalizePaymentMethod,
17
+ } from "../core/payments.js";
6
18
  import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
7
19
  import { formatRunResult } from "../core/formatters.js";
8
- import { storeFeedbackToken, getFeedbackToken } from "./_token-cache.js";
9
- import { initiateCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
20
+ import { storeFeedbackToken } from "./_token-cache.js";
21
+ import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
22
+ import {
23
+ formatPaymentChoicePrompt,
24
+ formatPaymentLabel,
25
+ formatRunConfirmationCommand,
26
+ resolveConfirmationMethod,
27
+ } from "./_payment-confirmation.js";
28
+ import type { AgentRecord } from "../core/types.js";
10
29
 
11
30
  const POLL_INTERVAL_MS = 3000;
12
- const POLL_MAX_MS = 120000; // 2 minutes
31
+ const POLL_MAX_MS = 120000;
13
32
 
14
33
  async function pollJobUntilDone(
15
34
  jobId: string,
16
- agentName: string,
17
35
  ): Promise<{ status: string; output?: unknown; error_code?: string }> {
18
36
  const deadline = Date.now() + POLL_MAX_MS;
19
37
  const walletAddress = await getWalletAddress();
@@ -34,9 +52,8 @@ async function pollJobUntilDone(
34
52
  if (job.status === "failed") {
35
53
  return { status: "failed", output: job.output, error_code: job.error_code };
36
54
  }
37
- // Still processing — continue polling
38
55
  } catch {
39
- // Ignore poll errors, keep trying until deadline
56
+ // Ignore poll errors until timeout.
40
57
  }
41
58
  }
42
59
 
@@ -51,17 +68,35 @@ function multiText(...blocks: string[]) {
51
68
  return { content: blocks.map((t) => ({ type: "text" as const, text: t })) };
52
69
  }
53
70
 
54
- // Pending confirmations: agent_id → { agent, input, method }
55
71
  const pendingRuns = new Map<string, {
56
72
  agent: { id: string; name: string; price: number };
57
73
  input: Record<string, unknown>;
58
74
  method?: string;
59
75
  }>();
60
76
 
77
+ function buildCreditPackOfferLines(agent: AgentRecord): string[] {
78
+ const program = getCreditPackProgram(agent);
79
+ if (!program?.packs?.length) return [];
80
+
81
+ return [
82
+ "Discounted credit packs:",
83
+ ...program.packs.map((pack) => formatCreditPackOffer({
84
+ pack_id: pack.key ?? "",
85
+ label: pack.name ?? pack.key ?? "Credit Pack",
86
+ included_units: pack.included_units ?? 0,
87
+ price_usd: pack.price_usd ?? "0.00",
88
+ effective_price_per_unit_usd: (pack.included_units ?? 0) > 0 && pack.price_usd
89
+ ? (Number(pack.price_usd) / (pack.included_units ?? 1)).toFixed(6)
90
+ : undefined,
91
+ })).map((line) => ` ${line}`),
92
+ ` Buy one with buy_agent_credit_pack({ agent_id: "${agent.id}", pack_id: "<pack_id>" })`,
93
+ ];
94
+ }
95
+
61
96
  export function registerRunTools(server: McpServer): void {
62
97
  server.tool(
63
98
  "run_agent",
64
- "Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking. If spending confirmation is enabled, first call returns a price quote — 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.",
99
+ "Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking. If spending confirmation is enabled, first call returns a price quote — 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 before execution.",
65
100
  {
66
101
  agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
67
102
  input: z.record(z.unknown()).describe("Input payload for the agent"),
@@ -71,51 +106,102 @@ export function registerRunTools(server: McpServer): void {
71
106
  async ({ agent_id, input, pay_with, confirmed }) => {
72
107
  if (!hasWalletConfigured()) {
73
108
  try {
74
- const { qr, url } = await initiateCardSetup();
75
- const blocks = formatCardSetupBlocks(qr, url);
76
- return multiText(...blocks);
109
+ const { url } = await getOrCreatePendingCardSetup();
110
+ return multiText(...formatCardSetupBlocks(url));
77
111
  } catch {
78
112
  return text(
79
113
  "No payment method configured.\n\n" +
80
114
  "To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
81
- "To use crypto: wallet_setup({ action: \"create\" })"
115
+ "To use crypto: wallet_setup({ action: \"create\" })",
82
116
  );
83
117
  }
84
118
  }
85
119
 
86
- // Resolve agent and fetch details
87
- let agent: { id: string; name?: string; pricePer1kTokens?: string; successRate?: number };
120
+ let agent: AgentRecord;
88
121
  try {
89
- agent = await apiGet<typeof agent>(`/agents/${agent_id}`);
122
+ agent = await apiGet<AgentRecord>(`/agents/${agent_id}`);
90
123
  } catch {
91
124
  return text(`Agent "${agent_id}" not found. Use search_agents to find available agents.`);
92
125
  }
93
126
 
94
127
  const price = parseFloat(agent.pricePer1kTokens ?? "0.01");
95
128
  const agentName = agent.name ?? agent_id;
129
+ const creditPackInventory = await getCreditPackInventory(agent.id);
130
+ const activeCreditPack = getActiveCreditPack(creditPackInventory);
131
+ const configuredMethods = getConfiguredMethods();
132
+ const compatibleMethods = getCompatiblePaymentMethods(agent, configuredMethods);
133
+ const pending = pendingRuns.get(agent.id);
134
+ const requestedMethod = pay_with ?? pending?.method;
135
+ const normalizedRequestedMethod = requestedMethod ? normalizePaymentMethod(requestedMethod) : null;
136
+
137
+ if (!activeCreditPack && requestedMethod && !normalizedRequestedMethod) {
138
+ return text(
139
+ `Payment method "${requestedMethod}" is not configured.\n\n` +
140
+ "Use wallet_status to review your current payment methods.",
141
+ );
142
+ }
143
+
144
+ if (!activeCreditPack && normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
145
+ return text(
146
+ `This agent cannot be paid with "${requestedMethod}".\n\n` +
147
+ `Available payment methods for this agent: ${compatibleMethods.join(", ") || "none"}.\n` +
148
+ "Use get_agent to inspect the agent details or choose another payment method.",
149
+ );
150
+ }
151
+
152
+ if (!activeCreditPack && !requestedMethod && compatibleMethods.length === 0) {
153
+ return text(
154
+ `No compatible payment methods are configured for ${agentName}.\n\n` +
155
+ `Your configured methods: ${configuredMethods.join(", ") || "none"}\n` +
156
+ `Agent accepts: ${agent.payment?.accepted_payments?.join(", ") || "unknown"}\n` +
157
+ "Use wallet_status to review your current setup.",
158
+ );
159
+ }
160
+
161
+ if (!activeCreditPack && !requestedMethod && compatibleMethods.length > 1) {
162
+ return text(
163
+ formatPaymentChoicePrompt(
164
+ agentName,
165
+ compatibleMethods,
166
+ compatibleMethods.map((method) => formatRunConfirmationCommand(agent.id, method).replace(", confirmed: true", "")),
167
+ ),
168
+ );
169
+ }
170
+
171
+ const method = activeCreditPack
172
+ ? undefined
173
+ : resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
96
174
 
97
- // Confirmation step: show price and wait for confirmed: true
98
- if (requiresSpendConfirmation() && !confirmed) {
175
+ if (!activeCreditPack && requiresSpendConfirmation() && !confirmed) {
99
176
  pendingRuns.set(agent.id, {
100
177
  agent: { id: agent.id, name: agentName, price },
101
178
  input,
102
- method: pay_with,
179
+ method,
103
180
  });
104
181
 
105
- return text([
182
+ const quoteLines = [
106
183
  `Ready to run ${agentName}`,
107
184
  "",
108
185
  ` Cost: $${price.toFixed(2)}`,
109
- ` Payment: ${pay_with ?? getConfiguredMethods()[0] ?? "auto"}`,
186
+ ` Payment: ${formatPaymentLabel(method)}`,
187
+ ];
188
+
189
+ const creditPackLines = buildCreditPackOfferLines(agent);
190
+ if (creditPackLines.length > 0) {
191
+ quoteLines.push("", ...creditPackLines);
192
+ }
193
+
194
+ quoteLines.push(
110
195
  "",
111
196
  "To proceed, call:",
112
- ` run_agent({ agent_id: "${agent.id}", input: <same>, confirmed: true })`,
197
+ formatRunConfirmationCommand(agent.id, method),
113
198
  "",
114
199
  "To cancel, do nothing.",
115
- ].join("\n"));
200
+ );
201
+
202
+ return text(quoteLines.join("\n"));
116
203
  }
117
204
 
118
- const method = pay_with;
119
205
  let processedInput: Record<string, unknown>;
120
206
  let uploadSummary = "";
121
207
  try {
@@ -123,27 +209,48 @@ export function registerRunTools(server: McpServer): void {
123
209
  processedInput = uploadResult.input;
124
210
  if (uploadResult.uploads.length > 0) {
125
211
  uploadSummary = uploadResult.uploads
126
- .map((u) => `Uploaded ${u.field}: ${u.originalPath} → ${u.url}`)
212
+ .map((upload) => `Uploaded ${upload.field}: ${upload.originalPath} → ${upload.url}`)
127
213
  .join("\n");
128
214
  }
129
215
  } catch (err) {
130
216
  const msg = err instanceof Error ? err.message : "File upload failed";
131
217
  return text(`Error: ${msg}`);
132
218
  }
219
+
133
220
  let result: Record<string, unknown>;
134
221
  try {
135
- result = await apiPostWithPayment<Record<string, unknown>>(
136
- `/agents/${agent.id}/run`,
137
- { input: processedInput },
138
- method,
139
- );
222
+ result = activeCreditPack
223
+ ? await apiPost<Record<string, unknown>>(
224
+ `/agents/${agent.id}/run`,
225
+ { input: processedInput },
226
+ { ensureConsumerPrincipal: true },
227
+ )
228
+ : await apiPostWithPayment<Record<string, unknown>>(
229
+ `/agents/${agent.id}/run`,
230
+ { input: processedInput },
231
+ method,
232
+ );
140
233
  } catch (err: unknown) {
141
234
  const apiErr = err as { status?: number; message?: string };
235
+ if (activeCreditPack && apiErr?.status === 402) {
236
+ return text(
237
+ `Your available credit packs for ${agentName} could not cover this run.\n\n` +
238
+ "Use list_agent_credit_packs to inspect your balances, or retry with a payment method.",
239
+ );
240
+ }
142
241
  if (apiErr?.status === 402) {
242
+ const allMethods = getConfiguredMethods();
243
+ const methodName = method ?? allMethods[0] ?? "auto";
244
+ const creditPackLines = buildCreditPackOfferLines(agent);
143
245
  return text(
144
- "Payment failed — your wallet may not have enough USDC.\n\n" +
145
- "Check your balance and fund your wallet, then try again.\n" +
146
- "Use wallet_status to check your current payment methods."
246
+ [
247
+ `Payment failed "${methodName}" payment was rejected.`,
248
+ "",
249
+ "Check your payment method and try again.",
250
+ ...(allMethods.length > 0 ? [`Configured methods: ${allMethods.join(", ")}`] : []),
251
+ "Use wallet_status to check your current payment methods.",
252
+ ...(creditPackLines.length > 0 ? ["", ...creditPackLines] : []),
253
+ ].join("\n"),
147
254
  );
148
255
  }
149
256
  const msg = apiErr?.message ?? "Failed to run agent";
@@ -153,7 +260,6 @@ export function registerRunTools(server: McpServer): void {
153
260
  return text(`Error: ${msg}`);
154
261
  }
155
262
 
156
- // Clean up pending confirmation
157
263
  pendingRuns.delete(agent.id);
158
264
 
159
265
  const formatted = formatRunResult(result, {
@@ -168,10 +274,10 @@ export function registerRunTools(server: McpServer): void {
168
274
 
169
275
  const actualCost = result.cost as number | undefined;
170
276
  const status = result.status as string;
277
+ const usedCreditPack = result.consumption_mode === "credit_pack";
171
278
 
172
- // Async agent — poll until complete
173
279
  if (status === "processing") {
174
- const pollResult = await pollJobUntilDone(jobId, agentName);
280
+ const pollResult = await pollJobUntilDone(jobId);
175
281
  if (pollResult.status === "completed") {
176
282
  const asyncFormatted = formatRunResult({
177
283
  ...result,
@@ -179,14 +285,15 @@ export function registerRunTools(server: McpServer): void {
179
285
  output: pollResult.output,
180
286
  }, { paymentMethod: method ?? getConfiguredMethods()[0] });
181
287
 
182
- if (result.feedback_token) {
183
- storeFeedbackToken(jobId, result.feedback_token as string, resultAgentId);
184
- }
288
+ const asyncSpendLine = usedCreditPack
289
+ ? "This run used your available credit pack."
290
+ : actualCost != null
291
+ ? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.`
292
+ : "This run completed successfully.";
185
293
 
186
- const asyncCostLine = actualCost != null ? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.` : "";
187
294
  const asyncFeedbackAsk = [
188
- `IMPORTANT: After presenting the result above, you MUST ask the user:`,
189
- `"${asyncCostLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
295
+ "IMPORTANT: After presenting the result above, you MUST ask the user:",
296
+ `"${asyncSpendLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
190
297
  "",
191
298
  `If the user wants to rate: rate_agent({ job_id: "${jobId}", rating: <1-5>, comment: "..." })`,
192
299
  `If the user wants to tip: tip_agent({ job_id: "${jobId}", agent_id: "${resultAgentId}", amount: 0.25 })`,
@@ -199,7 +306,6 @@ export function registerRunTools(server: McpServer): void {
199
306
  );
200
307
  }
201
308
 
202
- // Async agent failed
203
309
  const failedFormatted = formatRunResult({
204
310
  ...result,
205
311
  status: "failed",
@@ -209,11 +315,12 @@ export function registerRunTools(server: McpServer): void {
209
315
 
210
316
  return multiText(
211
317
  uploadSummary ? `${uploadSummary}\n\n${failedFormatted}` : failedFormatted,
212
- "The agent execution failed. A refund has been initiated automatically.",
318
+ usedCreditPack
319
+ ? "The agent execution failed and your reserved credit-pack unit was released automatically."
320
+ : "The agent execution failed. A refund has been initiated automatically.",
213
321
  );
214
322
  }
215
323
 
216
- // Auto-tip if configured and run succeeded
217
324
  const defaultTip = getDefaultTipAmount();
218
325
  let tipLine = "";
219
326
  if (status === "success" && defaultTip > 0 && result.feedback_token) {
@@ -233,18 +340,24 @@ export function registerRunTools(server: McpServer): void {
233
340
  if (status !== "success") {
234
341
  return multiText(
235
342
  uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted,
236
- "The agent execution failed. A refund has been initiated automatically.",
343
+ usedCreditPack
344
+ ? "The agent execution failed and your reserved credit-pack unit was released automatically."
345
+ : "The agent execution failed. A refund has been initiated automatically.",
237
346
  );
238
347
  }
239
348
 
240
- const costLine = actualCost != null ? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.` : "";
349
+ const costLine = usedCreditPack
350
+ ? ""
351
+ : actualCost != null
352
+ ? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.`
353
+ : "";
354
+ const spendLine = usedCreditPack ? "This run used your available credit pack." : (costLine || "This run completed successfully.");
241
355
 
242
- // Separate the feedback ask into its own content block so the LLM
243
- // surfaces it to the user as a distinct follow-up question, rather than
244
- // burying it inside a collapsed tool output.
245
356
  const feedbackAsk = [
246
- `IMPORTANT: After presenting the result above, you MUST ask the user:`,
247
- `"${costLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
357
+ `--- Job ${jobId} | ${agentName} | ${usedCreditPack ? "covered by credit pack" : (costLine || "completed")} ---`,
358
+ "",
359
+ "IMPORTANT: After presenting the result above, you MUST ask the user:",
360
+ `"${spendLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
248
361
  tipLine,
249
362
  "",
250
363
  `If the user wants to rate: rate_agent({ job_id: "${jobId}", rating: <1-5>, comment: "..." })`,
@@ -254,7 +367,15 @@ export function registerRunTools(server: McpServer): void {
254
367
  `To save for later: favorite_agent({ agent_id: "${resultAgentId}" })`,
255
368
  ].join("\n");
256
369
 
257
- return multiText(uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted, feedbackAsk);
370
+ const supplementalLines = !usedCreditPack
371
+ ? buildCreditPackOfferLines(agent)
372
+ : [];
373
+
374
+ const primaryBlock = uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted;
375
+ return multiText(
376
+ supplementalLines.length > 0 ? `${primaryBlock}\n\n${supplementalLines.join("\n")}` : primaryBlock,
377
+ feedbackAsk,
378
+ );
258
379
  },
259
380
  );
260
381
  }