@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.
- package/dist/core/__tests__/card-setup.test.d.ts +1 -0
- package/dist/core/__tests__/card-setup.test.js +99 -0
- package/dist/core/__tests__/formatters.test.d.ts +1 -0
- package/dist/core/__tests__/formatters.test.js +15 -0
- package/dist/core/__tests__/passes.test.d.ts +1 -0
- package/dist/core/__tests__/passes.test.js +82 -0
- package/dist/core/__tests__/payments.test.d.ts +1 -0
- package/dist/core/__tests__/payments.test.js +52 -0
- package/dist/core/__tests__/principal.test.d.ts +1 -0
- package/dist/core/__tests__/principal.test.js +67 -0
- package/dist/core/api-client.d.ts +9 -4
- package/dist/core/api-client.js +52 -22
- package/dist/core/card-setup.d.ts +20 -13
- package/dist/core/card-setup.js +85 -29
- package/dist/core/config.d.ts +3 -0
- package/dist/core/config.js +24 -2
- package/dist/core/formatters.d.ts +2 -0
- package/dist/core/formatters.js +5 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/ows-adapter.d.ts +10 -2
- package/dist/core/ows-adapter.js +54 -10
- package/dist/core/passes.d.ts +40 -0
- package/dist/core/passes.js +32 -0
- package/dist/core/payments.d.ts +8 -0
- package/dist/core/payments.js +97 -13
- package/dist/core/principal.d.ts +2 -0
- package/dist/core/principal.js +109 -0
- package/dist/core/solana-charge.d.ts +9 -0
- package/dist/core/solana-charge.js +95 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/index.js +8 -3
- package/dist/prompts/index.js +1 -1
- package/dist/resources/wallet.js +8 -1
- package/dist/tools/__tests__/_payment-confirmation.test.d.ts +1 -0
- package/dist/tools/__tests__/_payment-confirmation.test.js +30 -0
- package/dist/tools/_payment-confirmation.d.ts +6 -0
- package/dist/tools/_payment-confirmation.js +28 -0
- package/dist/tools/agent-info.js +14 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/passes.d.ts +2 -0
- package/dist/tools/passes.js +157 -0
- package/dist/tools/run.js +113 -51
- package/dist/tools/solve.js +102 -44
- package/dist/tools/wallet.js +85 -50
- package/package.json +3 -1
- package/src/core/__tests__/card-setup.test.ts +118 -0
- package/src/core/__tests__/formatters.test.ts +17 -0
- package/src/core/__tests__/passes.test.ts +94 -0
- package/src/core/__tests__/payments.test.ts +60 -0
- package/src/core/__tests__/principal.test.ts +87 -0
- package/src/core/api-client.ts +70 -23
- package/src/core/card-setup.ts +109 -34
- package/src/core/config.ts +29 -3
- package/src/core/formatters.ts +7 -1
- package/src/core/index.ts +2 -0
- package/src/core/ows-adapter.ts +74 -8
- package/src/core/passes.ts +74 -0
- package/src/core/payments.ts +113 -13
- package/src/core/principal.ts +128 -0
- package/src/core/solana-charge.ts +149 -0
- package/src/core/types.ts +10 -0
- package/src/index.ts +8 -3
- package/src/prompts/index.ts +1 -1
- package/src/resources/wallet.ts +8 -1
- package/src/tools/__tests__/_payment-confirmation.test.ts +45 -0
- package/src/tools/_payment-confirmation.ts +52 -0
- package/src/tools/agent-info.ts +23 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/passes.ts +234 -0
- package/src/tools/run.ts +171 -55
- package/src/tools/solve.ts +149 -56
- package/src/tools/wallet.ts +102 -52
package/src/tools/agent-info.ts
CHANGED
|
@@ -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)
|
package/src/tools/index.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
9
|
-
import {
|
|
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;
|
|
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
|
|
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
|
|
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 {
|
|
75
|
-
|
|
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
|
-
|
|
87
|
-
let agent: { id: string; name?: string; pricePer1kTokens?: string; successRate?: number };
|
|
120
|
+
let agent: AgentRecord;
|
|
88
121
|
try {
|
|
89
|
-
agent = await apiGet<
|
|
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
|
-
|
|
98
|
-
|
|
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
|
|
179
|
+
method,
|
|
103
180
|
});
|
|
104
181
|
|
|
105
|
-
|
|
182
|
+
const quoteLines = [
|
|
106
183
|
`Ready to run ${agentName}`,
|
|
107
184
|
"",
|
|
108
185
|
` Cost: $${price.toFixed(2)}`,
|
|
109
|
-
` Payment: ${
|
|
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
|
-
|
|
197
|
+
formatRunConfirmationCommand(agent.id, method),
|
|
113
198
|
"",
|
|
114
199
|
"To cancel, do nothing.",
|
|
115
|
-
|
|
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((
|
|
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 =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
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
|
-
|
|
186
|
-
|
|
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
|
-
|
|
192
|
-
`"${
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
252
|
-
`"${
|
|
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
|
-
|
|
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
|
}
|