@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
@@ -21,6 +21,15 @@ export function registerAgentInfoTools(server: McpServer): void {
21
21
  const s = (a.stats ?? {}) as Record<string, unknown>;
22
22
  const payment = (a.payment ?? {}) as Record<string, unknown>;
23
23
  const _pricing = (payment.pricing ?? {}) as Record<string, unknown>;
24
+ const creditPacks = payment.credit_packs as {
25
+ unit_type?: string;
26
+ packs?: Array<{
27
+ key?: string;
28
+ name?: string;
29
+ included_units?: number;
30
+ price_usd?: string;
31
+ }>;
32
+ } | null | undefined;
24
33
 
25
34
  const lines = [
26
35
  `${a.name}`,
@@ -41,6 +50,20 @@ export function registerAgentInfoTools(server: McpServer): void {
41
50
  const hint = outputTypeHint(a.tags as string[]);
42
51
  return hint ? [`Output: ${hint}`] : [];
43
52
  })(),
53
+ ...(() => {
54
+ if (!creditPacks?.packs?.length) return [];
55
+ const packLines = [
56
+ "",
57
+ `Discounted credit packs: ${creditPacks.unit_type ?? "run"}s`,
58
+ ];
59
+ for (const pack of creditPacks.packs) {
60
+ packLines.push(
61
+ ` ${pack.name ?? pack.key}: ${pack.included_units ?? 0} units for $${pack.price_usd ?? "0.00"}`,
62
+ );
63
+ }
64
+ packLines.push(" Use buy_agent_credit_pack to purchase one.");
65
+ return packLines;
66
+ })(),
44
67
  ];
45
68
 
46
69
  // Feedback summary (rating + tips)
@@ -7,3 +7,4 @@ export { registerRateTools } from "./rate.js";
7
7
  export { registerWalletTools } from "./wallet.js";
8
8
  export { registerFavoriteTools } from "./favorites.js";
9
9
  export { registerTipTools } from "./tip.js";
10
+ export { registerPassTools } from "./passes.js";
@@ -0,0 +1,234 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiGet, apiPostWithPayment } from "../core/api-client.js";
4
+ import {
5
+ formatCreditPack,
6
+ formatCreditPackOffer,
7
+ getCreditPackProgram,
8
+ } from "../core/passes.js";
9
+ import {
10
+ getCompatiblePaymentMethods,
11
+ getConfiguredMethods,
12
+ hasWalletConfigured,
13
+ normalizePaymentMethod,
14
+ } from "../core/payments.js";
15
+ import { requiresSpendConfirmation } from "../core/config.js";
16
+ import { ensureConsumerPrincipal } from "../core/principal.js";
17
+ import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
18
+ import {
19
+ formatPaymentChoicePrompt,
20
+ formatPaymentLabel,
21
+ resolveConfirmationMethod,
22
+ } from "./_payment-confirmation.js";
23
+ import type { AgentRecord } from "../core/types.js";
24
+ import type { CreditPackInventory, CreditPackOffer, CreditPackRecord } from "../core/passes.js";
25
+
26
+ const pendingCreditPackPurchases = new Map<string, {
27
+ agentId: string;
28
+ agentName: string;
29
+ offer: CreditPackOffer;
30
+ method?: string;
31
+ }>();
32
+
33
+ function text(t: string) {
34
+ return { content: [{ type: "text" as const, text: t }] };
35
+ }
36
+
37
+ function multiText(...blocks: string[]) {
38
+ return { content: blocks.map((t) => ({ type: "text" as const, text: t })) };
39
+ }
40
+
41
+ async function getAgent(agentId: string): Promise<AgentRecord> {
42
+ return apiGet<AgentRecord>(`/agents/${agentId}`);
43
+ }
44
+
45
+ function findOffer(agent: AgentRecord, packId: string): CreditPackOffer | null {
46
+ const program = getCreditPackProgram(agent);
47
+ const pack = program?.packs?.find((candidate) => candidate.key === packId);
48
+ if (!pack) return null;
49
+
50
+ const price = Number(pack.price_usd ?? "0");
51
+ const units = pack.included_units ?? 0;
52
+ return {
53
+ pack_id: packId,
54
+ label: pack.name ?? pack.key ?? "Credit Pack",
55
+ included_units: units,
56
+ price_usd: price.toFixed(2),
57
+ effective_price_per_unit_usd: units > 0 ? (price / units).toFixed(6) : undefined,
58
+ };
59
+ }
60
+
61
+ export function registerPassTools(server: McpServer): void {
62
+ server.tool(
63
+ "buy_agent_credit_pack",
64
+ "Purchase a discounted prepaid credit pack for an agent. Credit packs are agent-specific and automatically cover future runs until the included units run out.",
65
+ {
66
+ agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
67
+ pack_id: z.string().optional().describe("Specific pack key to buy, like 'starter' or 'growth'. If omitted and only one pack exists, it is selected automatically."),
68
+ pay_with: z.string().optional().describe("Payment method — wallet ID, chain name, or 'card'. Auto-detected if omitted."),
69
+ confirmed: z.boolean().optional().describe("Set to true to confirm the purchase after seeing the quote."),
70
+ },
71
+ async ({ agent_id, pack_id, pay_with, confirmed }) => {
72
+ if (!hasWalletConfigured()) {
73
+ try {
74
+ const { url } = await getOrCreatePendingCardSetup();
75
+ return multiText(...formatCardSetupBlocks(url));
76
+ } catch {
77
+ return text(
78
+ "No payment method configured.\n\n" +
79
+ "To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
80
+ "To use crypto: wallet_setup({ action: \"create\" })",
81
+ );
82
+ }
83
+ }
84
+
85
+ const agent = await getAgent(agent_id);
86
+ const agentName = agent.name ?? agent_id;
87
+ const principal = await ensureConsumerPrincipal();
88
+ const program = getCreditPackProgram(agent);
89
+ const offers = (program?.packs ?? [])
90
+ .map((pack) => findOffer(agent, pack.key ?? ""))
91
+ .filter((offer): offer is CreditPackOffer => !!offer);
92
+
93
+ if (offers.length === 0) {
94
+ return text(`${agentName} does not currently offer discounted credit packs.`);
95
+ }
96
+
97
+ const configuredMethods = getConfiguredMethods();
98
+ const compatibleMethods = getCompatiblePaymentMethods(agent, configuredMethods);
99
+ const pending = pendingCreditPackPurchases.get(agent.id);
100
+ const requestedMethod = pay_with ?? pending?.method;
101
+ const normalizedRequestedMethod = requestedMethod ? normalizePaymentMethod(requestedMethod) : null;
102
+
103
+ if (requestedMethod && !normalizedRequestedMethod) {
104
+ return text(
105
+ `Payment method "${requestedMethod}" is not configured.\n\n` +
106
+ "Use wallet_status to review your current payment methods.",
107
+ );
108
+ }
109
+
110
+ if (normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
111
+ return text(
112
+ `This agent cannot be paid with "${requestedMethod}".\n\n` +
113
+ `Available payment methods for this agent: ${compatibleMethods.join(", ") || "none"}.`,
114
+ );
115
+ }
116
+
117
+ if (!requestedMethod && compatibleMethods.length === 0) {
118
+ return text(
119
+ `No compatible payment methods are configured for ${agentName}.\n\n` +
120
+ `Your configured methods: ${configuredMethods.join(", ") || "none"}`,
121
+ );
122
+ }
123
+
124
+ if (!requestedMethod && compatibleMethods.length > 1) {
125
+ return text(
126
+ formatPaymentChoicePrompt(
127
+ `${agentName} credit-pack purchase`,
128
+ compatibleMethods,
129
+ compatibleMethods.map((method) => ` buy_agent_credit_pack({ agent_id: "${agent.id}"${pack_id ? `, pack_id: "${pack_id}"` : ""}, pay_with: "${method}" })`),
130
+ ),
131
+ );
132
+ }
133
+
134
+ let resolvedOffer: CreditPackOffer;
135
+ if (pack_id) {
136
+ const offer = offers.find((candidate) => candidate.pack_id === pack_id);
137
+ if (!offer) {
138
+ return text(
139
+ `Unknown credit pack "${pack_id}" for ${agentName}.\n\n` +
140
+ `Available packs:\n${offers.map((offer) => ` ${formatCreditPackOffer(offer)}`).join("\n")}`,
141
+ );
142
+ }
143
+ resolvedOffer = offer;
144
+ } else if (offers.length === 1) {
145
+ resolvedOffer = offers[0]!;
146
+ } else {
147
+ return text([
148
+ `Multiple credit packs are available for ${agentName}.`,
149
+ "",
150
+ ...offers.map((offer) => ` ${offer.pack_id}: ${formatCreditPackOffer(offer)}`),
151
+ "",
152
+ "Choose one by calling:",
153
+ ` buy_agent_credit_pack({ agent_id: "${agent.id}", pack_id: "${offers[0]!.pack_id}" })`,
154
+ ].join("\n"));
155
+ }
156
+
157
+ const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
158
+
159
+ if (requiresSpendConfirmation() && !confirmed) {
160
+ pendingCreditPackPurchases.set(agent.id, {
161
+ agentId: agent.id,
162
+ agentName,
163
+ offer: resolvedOffer,
164
+ method,
165
+ });
166
+
167
+ return text([
168
+ `Ready to buy a credit pack for ${agentName}`,
169
+ "",
170
+ formatCreditPackOffer(resolvedOffer),
171
+ `Payment: ${formatPaymentLabel(method)}`,
172
+ `Consumer principal: ${principal}`,
173
+ "",
174
+ "To proceed, call:",
175
+ ` buy_agent_credit_pack({ agent_id: "${agent.id}", pack_id: "${resolvedOffer.pack_id}", pay_with: "${method}", confirmed: true })`,
176
+ "",
177
+ "To cancel, do nothing.",
178
+ ].join("\n"));
179
+ }
180
+
181
+ const result = await apiPostWithPayment<{
182
+ consumer_principal: string;
183
+ offer: CreditPackOffer;
184
+ credit_pack: CreditPackRecord;
185
+ }>(
186
+ `/agents/${agent.id}/credit-packs/purchase`,
187
+ { pack_id: resolvedOffer.pack_id },
188
+ method,
189
+ { ensureConsumerPrincipal: true },
190
+ );
191
+
192
+ pendingCreditPackPurchases.delete(agent.id);
193
+
194
+ return text([
195
+ `Credit pack purchased for ${agentName}`,
196
+ "",
197
+ formatCreditPackOffer(result.offer),
198
+ formatCreditPack(result.credit_pack),
199
+ `Consumer principal: ${result.consumer_principal}`,
200
+ "",
201
+ "Future runs through run_agent will automatically use this credit pack while units remain.",
202
+ ].join("\n"));
203
+ },
204
+ );
205
+
206
+ server.tool(
207
+ "list_agent_credit_packs",
208
+ "Show discounted credit-pack offers for an agent plus any balances available under the current consumer principal.",
209
+ {
210
+ agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
211
+ },
212
+ async ({ agent_id }) => {
213
+ const agent = await getAgent(agent_id);
214
+ const result = await apiGet<CreditPackInventory>(`/agents/${agent.id}/credit-packs`, { ensureConsumerPrincipal: true });
215
+
216
+ const lines = [
217
+ `Credit packs for ${agent.name}`,
218
+ ...(result.consumer_principal ? [`Consumer principal: ${result.consumer_principal}`] : []),
219
+ ];
220
+
221
+ if (result.offers.length > 0) {
222
+ lines.push("", "Available packs:", ...result.offers.map((offer) => ` ${offer.pack_id}: ${formatCreditPackOffer(offer)}`));
223
+ }
224
+
225
+ if (result.balances.length > 0) {
226
+ lines.push("", "Your balances:", ...result.balances.map((pack) => ` ${formatCreditPack(pack)}`));
227
+ } else {
228
+ lines.push("", "No purchased credit packs found for this agent.");
229
+ }
230
+
231
+ return text(lines.join("\n"));
232
+ },
233
+ );
234
+ }
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
+ }
96
170
 
97
- // Confirmation step: show price and wait for confirmed: true
98
- if (requiresSpendConfirmation() && !confirmed) {
171
+ const method = activeCreditPack
172
+ ? undefined
173
+ : resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
174
+
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,30 +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) {
143
242
  const allMethods = getConfiguredMethods();
144
243
  const methodName = method ?? allMethods[0] ?? "auto";
244
+ const creditPackLines = buildCreditPackOfferLines(agent);
145
245
  return text(
146
- `Payment failed — "${methodName}" payment was rejected.\n\n` +
147
- "Check your payment method and try again.\n" +
148
- (allMethods.length > 0 ? `Configured methods: ${allMethods.join(", ")}\n` : "") +
149
- "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"),
150
254
  );
151
255
  }
152
256
  const msg = apiErr?.message ?? "Failed to run agent";
@@ -156,7 +260,6 @@ export function registerRunTools(server: McpServer): void {
156
260
  return text(`Error: ${msg}`);
157
261
  }
158
262
 
159
- // Clean up pending confirmation
160
263
  pendingRuns.delete(agent.id);
161
264
 
162
265
  const formatted = formatRunResult(result, {
@@ -171,10 +274,10 @@ export function registerRunTools(server: McpServer): void {
171
274
 
172
275
  const actualCost = result.cost as number | undefined;
173
276
  const status = result.status as string;
277
+ const usedCreditPack = result.consumption_mode === "credit_pack";
174
278
 
175
- // Async agent — poll until complete
176
279
  if (status === "processing") {
177
- const pollResult = await pollJobUntilDone(jobId, agentName);
280
+ const pollResult = await pollJobUntilDone(jobId);
178
281
  if (pollResult.status === "completed") {
179
282
  const asyncFormatted = formatRunResult({
180
283
  ...result,
@@ -182,14 +285,15 @@ export function registerRunTools(server: McpServer): void {
182
285
  output: pollResult.output,
183
286
  }, { paymentMethod: method ?? getConfiguredMethods()[0] });
184
287
 
185
- if (result.feedback_token) {
186
- storeFeedbackToken(jobId, result.feedback_token as string, resultAgentId);
187
- }
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.";
188
293
 
189
- const asyncCostLine = actualCost != null ? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.` : "";
190
294
  const asyncFeedbackAsk = [
191
- `IMPORTANT: After presenting the result above, you MUST ask the user:`,
192
- `"${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."`,
193
297
  "",
194
298
  `If the user wants to rate: rate_agent({ job_id: "${jobId}", rating: <1-5>, comment: "..." })`,
195
299
  `If the user wants to tip: tip_agent({ job_id: "${jobId}", agent_id: "${resultAgentId}", amount: 0.25 })`,
@@ -202,7 +306,6 @@ export function registerRunTools(server: McpServer): void {
202
306
  );
203
307
  }
204
308
 
205
- // Async agent failed
206
309
  const failedFormatted = formatRunResult({
207
310
  ...result,
208
311
  status: "failed",
@@ -212,11 +315,12 @@ export function registerRunTools(server: McpServer): void {
212
315
 
213
316
  return multiText(
214
317
  uploadSummary ? `${uploadSummary}\n\n${failedFormatted}` : failedFormatted,
215
- "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.",
216
321
  );
217
322
  }
218
323
 
219
- // Auto-tip if configured and run succeeded
220
324
  const defaultTip = getDefaultTipAmount();
221
325
  let tipLine = "";
222
326
  if (status === "success" && defaultTip > 0 && result.feedback_token) {
@@ -236,20 +340,24 @@ export function registerRunTools(server: McpServer): void {
236
340
  if (status !== "success") {
237
341
  return multiText(
238
342
  uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted,
239
- "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.",
240
346
  );
241
347
  }
242
348
 
243
- 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.");
244
355
 
245
- // Separate the feedback ask into its own content block so the LLM
246
- // surfaces it to the user as a distinct follow-up question, rather than
247
- // burying it inside a collapsed tool output.
248
356
  const feedbackAsk = [
249
- `--- Job ${jobId} | ${agentName} | ${costLine} ---`,
357
+ `--- Job ${jobId} | ${agentName} | ${usedCreditPack ? "covered by credit pack" : (costLine || "completed")} ---`,
250
358
  "",
251
- `IMPORTANT: After presenting the result above, you MUST ask the user:`,
252
- `"${costLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
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."`,
253
361
  tipLine,
254
362
  "",
255
363
  `If the user wants to rate: rate_agent({ job_id: "${jobId}", rating: <1-5>, comment: "..." })`,
@@ -259,7 +367,15 @@ export function registerRunTools(server: McpServer): void {
259
367
  `To save for later: favorite_agent({ agent_id: "${resultAgentId}" })`,
260
368
  ].join("\n");
261
369
 
262
- 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
+ );
263
379
  },
264
380
  );
265
381
  }