@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.
- 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 +87 -30
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +28 -1
- 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 +121 -16
- 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 +13 -4
- 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 +116 -49
- 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 +112 -35
- package/src/core/config.ts +33 -2
- 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 +140 -15
- 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 +13 -4
- 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 +174 -53
- package/src/tools/solve.ts +149 -56
- package/src/tools/wallet.ts +102 -52
package/dist/tools/solve.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
|
|
3
|
-
import {
|
|
4
|
-
import { hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, getWalletAddress, } from "../core/payments.js";
|
|
3
|
+
import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
|
|
4
|
+
import { getCompatiblePaymentMethods, hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, getWalletAddress, normalizePaymentMethod, toRegistryPaymentMethod, } from "../core/payments.js";
|
|
5
5
|
import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
|
|
6
6
|
import { agentList, formatRunResult } from "../core/formatters.js";
|
|
7
7
|
import { uploadLocalFiles } from "../core/file-upload.js";
|
|
8
8
|
import { storeFeedbackToken } from "./_token-cache.js";
|
|
9
|
+
import { formatPaymentChoicePrompt, formatPaymentLabel, formatSolveConfirmationCommand, makeSolvePendingKey, resolveConfirmationMethod, } from "./_payment-confirmation.js";
|
|
10
|
+
import { getActiveCreditPack, getCreditPackInventory, getCreditPackProgram } from "../core/passes.js";
|
|
9
11
|
const POLL_INTERVAL_MS = 3000;
|
|
10
12
|
const POLL_MAX_MS = 120000;
|
|
11
13
|
async function pollSolveJob(jobId) {
|
|
@@ -24,7 +26,7 @@ async function pollSolveJob(jobId) {
|
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
catch {
|
|
27
|
-
// Ignore poll errors
|
|
29
|
+
// Ignore poll errors and keep trying.
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
return { status: "failed", error_code: "POLL_TIMEOUT" };
|
|
@@ -35,11 +37,12 @@ function text(t) {
|
|
|
35
37
|
function multiText(...blocks) {
|
|
36
38
|
return { content: blocks.map((t) => ({ type: "text", text: t })) };
|
|
37
39
|
}
|
|
40
|
+
const pendingSolves = new Map();
|
|
38
41
|
function feedbackAsk(jobId, agentId, cost, tipSent) {
|
|
39
42
|
const defaultTip = getDefaultTipAmount();
|
|
40
|
-
const costLine = cost != null ? `Paid $${cost.toFixed(cost < 0.01 ? 4 : 2)}.` : "";
|
|
43
|
+
const costLine = cost != null ? `Paid $${cost.toFixed(cost < 0.01 ? 4 : 2)}.` : "This run used your available credit pack.";
|
|
41
44
|
return [
|
|
42
|
-
|
|
45
|
+
"IMPORTANT: After presenting the result above, you MUST ask the user:",
|
|
43
46
|
`"${costLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
|
|
44
47
|
tipSent ?? "",
|
|
45
48
|
"",
|
|
@@ -67,8 +70,22 @@ async function autoTip(jobId, agentId, agentName, feedbackToken) {
|
|
|
67
70
|
return `Auto-tip of $${defaultTip.toFixed(2)} failed.`;
|
|
68
71
|
}
|
|
69
72
|
}
|
|
73
|
+
function buildCreditPackSummary(agent) {
|
|
74
|
+
const creditPacks = getCreditPackProgram(agent);
|
|
75
|
+
if (!creditPacks?.packs?.length)
|
|
76
|
+
return [];
|
|
77
|
+
return [
|
|
78
|
+
"Discounted credit packs available:",
|
|
79
|
+
...creditPacks.packs.map((pack) => {
|
|
80
|
+
const price = Number(pack.price_usd ?? "0");
|
|
81
|
+
const units = pack.included_units ?? 0;
|
|
82
|
+
const unitPrice = units > 0 ? ` ($${(price / units).toFixed(2)}/unit)` : "";
|
|
83
|
+
return ` ${pack.name ?? pack.key}: ${units} units for $${(pack.price_usd ?? "0.00")}${unitPrice}`;
|
|
84
|
+
}),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
70
87
|
export function registerSolveTools(server) {
|
|
71
|
-
server.tool("solve", "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
|
|
88
|
+
server.tool("solve", "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.", {
|
|
72
89
|
intent: z
|
|
73
90
|
.string()
|
|
74
91
|
.describe("What you want to accomplish (natural language)"),
|
|
@@ -95,9 +112,8 @@ export function registerSolveTools(server) {
|
|
|
95
112
|
}, async ({ intent, input, budget, pay_with, confirmed }) => {
|
|
96
113
|
if (!hasWalletConfigured()) {
|
|
97
114
|
try {
|
|
98
|
-
const {
|
|
99
|
-
|
|
100
|
-
return { content: blocks.map((t) => ({ type: "text", text: t })) };
|
|
115
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
116
|
+
return multiText(...formatCardSetupBlocks(url));
|
|
101
117
|
}
|
|
102
118
|
catch {
|
|
103
119
|
return text("No payment method configured.\n\n" +
|
|
@@ -105,8 +121,15 @@ export function registerSolveTools(server) {
|
|
|
105
121
|
"To use crypto: wallet_setup({ action: \"create\" })");
|
|
106
122
|
}
|
|
107
123
|
}
|
|
108
|
-
const
|
|
109
|
-
|
|
124
|
+
const pendingKey = makeSolvePendingKey(intent, input, budget);
|
|
125
|
+
const pending = pendingSolves.get(pendingKey);
|
|
126
|
+
const configuredMethods = getConfiguredMethods();
|
|
127
|
+
const requestedMethod = pay_with ?? pending?.method;
|
|
128
|
+
const normalizedRequestedMethod = requestedMethod ? normalizePaymentMethod(requestedMethod) : null;
|
|
129
|
+
if (requestedMethod && !normalizedRequestedMethod) {
|
|
130
|
+
return text(`Payment method "${requestedMethod}" is not configured.\n\n` +
|
|
131
|
+
"Use wallet_status to review your current payment methods.");
|
|
132
|
+
}
|
|
110
133
|
let processedInput;
|
|
111
134
|
try {
|
|
112
135
|
const uploadResult = await uploadLocalFiles(input);
|
|
@@ -129,8 +152,9 @@ export function registerSolveTools(server) {
|
|
|
129
152
|
storeFeedbackToken(jobId, result.feedback_token, agentId);
|
|
130
153
|
}
|
|
131
154
|
const cost = result.cost;
|
|
155
|
+
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
132
156
|
const tipMsg = result.feedback_token ? await autoTip(jobId, agentId, agentName, result.feedback_token) : "";
|
|
133
|
-
return multiText(formatRunResult(result), feedbackAsk(jobId, agentId, cost, tipMsg));
|
|
157
|
+
return multiText(formatRunResult(result), feedbackAsk(jobId, agentId, usedCreditPack ? undefined : cost, tipMsg));
|
|
134
158
|
}
|
|
135
159
|
catch (err) {
|
|
136
160
|
const isAuthError = err instanceof Error &&
|
|
@@ -139,9 +163,10 @@ export function registerSolveTools(server) {
|
|
|
139
163
|
if (!isAuthError || !hasWalletConfigured())
|
|
140
164
|
throw err;
|
|
141
165
|
}
|
|
142
|
-
// Path 2: Wallet-only discovery — find agents, pick best, pay via MPP
|
|
143
166
|
const params = new URLSearchParams({ q: intent, limit: "5" });
|
|
144
|
-
const acceptedMethods =
|
|
167
|
+
const acceptedMethods = requestedMethod
|
|
168
|
+
? [toRegistryPaymentMethod(requestedMethod)].filter((value) => !!value)
|
|
169
|
+
: getAcceptedPaymentMethods();
|
|
145
170
|
if (acceptedMethods.length > 0) {
|
|
146
171
|
params.set("accepted_payment_methods", acceptedMethods.join(","));
|
|
147
172
|
}
|
|
@@ -149,64 +174,93 @@ export function registerSolveTools(server) {
|
|
|
149
174
|
if (!agents || agents.length === 0) {
|
|
150
175
|
return text(`No agents found matching "${intent}".`);
|
|
151
176
|
}
|
|
152
|
-
// Show discovery results
|
|
153
177
|
const discovery = agentList(agents, intent);
|
|
154
|
-
// Pick cheapest agent within budget
|
|
155
178
|
const inputTokens = Math.ceil(JSON.stringify(input).length / 4);
|
|
156
|
-
const affordable = agents.filter((
|
|
157
|
-
const price = parseFloat(
|
|
158
|
-
const cost =
|
|
179
|
+
const affordable = agents.filter((agent) => {
|
|
180
|
+
const price = parseFloat(agent.pricePer1kTokens ?? "0.01");
|
|
181
|
+
const cost = agent.pricingModel === "fixed" ? price : (inputTokens / 1000) * price;
|
|
159
182
|
return cost <= budget;
|
|
160
183
|
});
|
|
161
184
|
const selected = affordable[0] ?? agents[0];
|
|
185
|
+
const activeCreditPack = await getCreditPackInventory(selected.id).then(getActiveCreditPack);
|
|
186
|
+
const compatibleMethods = getCompatiblePaymentMethods(selected, configuredMethods);
|
|
187
|
+
if (!activeCreditPack && normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
|
|
188
|
+
return text(`The best matching agent cannot be paid with "${requestedMethod}".\n\n` +
|
|
189
|
+
`Available payment methods for ${selected.name}: ${compatibleMethods.join(", ") || "none"}.\n` +
|
|
190
|
+
"Choose another payment method or refine your search.");
|
|
191
|
+
}
|
|
192
|
+
if (!activeCreditPack && !requestedMethod && compatibleMethods.length === 0) {
|
|
193
|
+
return text(`No compatible payment methods are configured for ${selected.name}.\n\n` +
|
|
194
|
+
`Your configured methods: ${configuredMethods.join(", ") || "none"}\n` +
|
|
195
|
+
`Agent accepts: ${selected.payment?.accepted_payments?.join(", ") || "unknown"}\n` +
|
|
196
|
+
"Use wallet_status to review your current setup.");
|
|
197
|
+
}
|
|
198
|
+
if (!activeCreditPack && !requestedMethod && compatibleMethods.length > 1) {
|
|
199
|
+
return text([
|
|
200
|
+
discovery,
|
|
201
|
+
"",
|
|
202
|
+
formatPaymentChoicePrompt(selected.name ?? selected.id, compatibleMethods, compatibleMethods.map((method) => formatSolveConfirmationCommand(intent, budget, method).replace(", confirmed: true", ""))),
|
|
203
|
+
].join("\n"));
|
|
204
|
+
}
|
|
205
|
+
const method = activeCreditPack
|
|
206
|
+
? undefined
|
|
207
|
+
: resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
|
|
162
208
|
const selectedPrice = parseFloat(selected.pricePer1kTokens ?? "0.01");
|
|
163
209
|
const estimatedCost = selected.pricingModel === "fixed"
|
|
164
210
|
? selectedPrice
|
|
165
211
|
: (inputTokens / 1000) * selectedPrice;
|
|
166
|
-
|
|
167
|
-
|
|
212
|
+
if (!activeCreditPack && requiresSpendConfirmation() && !confirmed) {
|
|
213
|
+
pendingSolves.set(pendingKey, { method });
|
|
168
214
|
return text([
|
|
169
215
|
discovery,
|
|
170
216
|
"",
|
|
171
217
|
`Best match: ${selected.name}`,
|
|
172
218
|
`Cost: $${estimatedCost.toFixed(2)}`,
|
|
173
|
-
`Payment: ${method}`,
|
|
219
|
+
`Payment: ${formatPaymentLabel(method)}`,
|
|
220
|
+
...(() => {
|
|
221
|
+
const summary = buildCreditPackSummary(selected);
|
|
222
|
+
return summary.length > 0 ? ["", ...summary] : [];
|
|
223
|
+
})(),
|
|
174
224
|
"",
|
|
175
225
|
"To proceed, call:",
|
|
176
|
-
|
|
226
|
+
formatSolveConfirmationCommand(intent, budget, method),
|
|
177
227
|
"",
|
|
178
228
|
"To cancel, do nothing.",
|
|
179
229
|
].join("\n"));
|
|
180
230
|
}
|
|
181
231
|
let result;
|
|
182
|
-
let processedInput2;
|
|
183
232
|
try {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
catch (err) {
|
|
188
|
-
const msg = err instanceof Error ? err.message : "File upload failed";
|
|
189
|
-
return text(`Error: ${msg}`);
|
|
190
|
-
}
|
|
191
|
-
try {
|
|
192
|
-
result = await apiPostWithPayment(`/agents/${selected.id}/run`, { input: processedInput2 }, method);
|
|
233
|
+
result = activeCreditPack
|
|
234
|
+
? await apiPost(`/agents/${selected.id}/run`, { input: processedInput }, { ensureConsumerPrincipal: true })
|
|
235
|
+
: await apiPostWithPayment(`/agents/${selected.id}/run`, { input: processedInput }, method);
|
|
193
236
|
}
|
|
194
237
|
catch (err) {
|
|
195
238
|
const apiErr = err;
|
|
239
|
+
if (activeCreditPack && apiErr?.status === 402) {
|
|
240
|
+
return text(`Your available credit packs for ${selected.name} could not cover this run.\n\n` +
|
|
241
|
+
"Use list_agent_credit_packs to inspect your balances, or retry with a payment method.");
|
|
242
|
+
}
|
|
196
243
|
if (apiErr?.status === 402) {
|
|
197
|
-
return text(
|
|
198
|
-
"
|
|
199
|
-
"
|
|
244
|
+
return text([
|
|
245
|
+
"Payment failed — your wallet may not have enough funds or the selected method was rejected.",
|
|
246
|
+
"",
|
|
247
|
+
"Check your balance and try again.",
|
|
248
|
+
"Use wallet_status to check your current payment methods.",
|
|
249
|
+
...(() => {
|
|
250
|
+
const summary = buildCreditPackSummary(selected);
|
|
251
|
+
return summary.length > 0 ? ["", ...summary] : [];
|
|
252
|
+
})(),
|
|
253
|
+
].join("\n"));
|
|
200
254
|
}
|
|
201
255
|
return text(`Error: ${apiErr?.message ?? "Failed to run agent"}`);
|
|
202
256
|
}
|
|
257
|
+
pendingSolves.delete(pendingKey);
|
|
203
258
|
const jobId = result.job_id ?? "";
|
|
204
|
-
const
|
|
259
|
+
const agentId = result.agent_id ?? selected.id;
|
|
205
260
|
const status = result.status;
|
|
206
261
|
if (result.feedback_token) {
|
|
207
|
-
storeFeedbackToken(jobId, result.feedback_token,
|
|
262
|
+
storeFeedbackToken(jobId, result.feedback_token, agentId);
|
|
208
263
|
}
|
|
209
|
-
// Async agent — poll until complete
|
|
210
264
|
if (status === "processing") {
|
|
211
265
|
const pollResult = await pollSolveJob(jobId);
|
|
212
266
|
if (pollResult.status === "completed") {
|
|
@@ -224,9 +278,9 @@ export function registerSolveTools(server) {
|
|
|
224
278
|
asyncFormatted,
|
|
225
279
|
].join("\n");
|
|
226
280
|
const asyncCost = result.cost;
|
|
227
|
-
|
|
281
|
+
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
282
|
+
return multiText(asyncOutput, feedbackAsk(jobId, agentId, usedCreditPack ? undefined : asyncCost, ""));
|
|
228
283
|
}
|
|
229
|
-
// Async agent failed
|
|
230
284
|
const failedFormatted = formatRunResult({
|
|
231
285
|
...result,
|
|
232
286
|
status: "failed",
|
|
@@ -240,10 +294,14 @@ export function registerSolveTools(server) {
|
|
|
240
294
|
"",
|
|
241
295
|
failedFormatted,
|
|
242
296
|
].join("\n");
|
|
243
|
-
|
|
297
|
+
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
298
|
+
return multiText(failedOutput, usedCreditPack
|
|
299
|
+
? "The agent execution failed and the reserved credit-pack unit was released automatically."
|
|
300
|
+
: "The agent execution failed. A refund has been initiated automatically.");
|
|
244
301
|
}
|
|
245
302
|
const actualCost = result.cost;
|
|
246
|
-
const
|
|
303
|
+
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
304
|
+
const tipMsg = result.feedback_token ? await autoTip(jobId, agentId, selected.name ?? "", result.feedback_token) : "";
|
|
247
305
|
const output = [
|
|
248
306
|
discovery,
|
|
249
307
|
"",
|
|
@@ -252,6 +310,6 @@ export function registerSolveTools(server) {
|
|
|
252
310
|
"",
|
|
253
311
|
formatRunResult(result, { paymentMethod: method }),
|
|
254
312
|
].join("\n");
|
|
255
|
-
return multiText(output, feedbackAsk(jobId,
|
|
313
|
+
return multiText(output, feedbackAsk(jobId, agentId, usedCreditPack ? undefined : actualCost, tipMsg));
|
|
256
314
|
});
|
|
257
315
|
}
|
package/dist/tools/wallet.js
CHANGED
|
@@ -1,30 +1,50 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { getWallets, getCardConfig, setCardConfig, addWallet } from "../core/config.js";
|
|
2
|
+
import { getWallets, getCardConfig, setCardConfig, addWallet, getPendingCardSetupToken, } from "../core/config.js";
|
|
3
3
|
import { getWalletAddress } from "../core/payments.js";
|
|
4
|
-
import {
|
|
5
|
-
import { isOwsAvailable, createOwsWallet, importKeyToOws, listOwsWallets, } from "../core/ows-adapter.js";
|
|
4
|
+
import { getOrCreatePendingCardSetup, formatCardSetupBlocks, getCardCapabilities, pollCardSetup, } from "../core/card-setup.js";
|
|
5
|
+
import { isOwsAvailable, createOwsWallet, importKeyToOws, listOwsWallets, listOwsWalletsByChain, } from "../core/ows-adapter.js";
|
|
6
|
+
import { ensureConsumerPrincipal, getConsumerPrincipal } from "../core/principal.js";
|
|
6
7
|
function text(t) {
|
|
7
8
|
return { content: [{ type: "text", text: t }] };
|
|
8
9
|
}
|
|
9
|
-
// Pending card setup — stored between the QR display call and the confirmation poll
|
|
10
|
-
let pendingCardSetup = null;
|
|
11
10
|
export function registerWalletTools(server) {
|
|
12
11
|
// ── wallet_status (extracted from check_wallet) ─────────────────
|
|
13
12
|
server.tool("wallet_status", "Check payment readiness. Shows all configured wallets, their chains, addresses, and card status.", {}, async () => {
|
|
14
13
|
const wallets = getWallets();
|
|
15
14
|
const card = getCardConfig();
|
|
16
|
-
|
|
15
|
+
const pendingCardSetupToken = getPendingCardSetupToken();
|
|
16
|
+
const consumerPrincipal = await getConsumerPrincipal();
|
|
17
|
+
if (wallets.length === 0 && !card && !pendingCardSetupToken) {
|
|
17
18
|
return text("No payment methods configured.\nUse wallet_setup to create or import a wallet.");
|
|
18
19
|
}
|
|
19
20
|
const lines = ["Payment methods:"];
|
|
20
21
|
for (const w of wallets) {
|
|
21
|
-
const addr = await getWalletAddress(w.id);
|
|
22
22
|
const label = w.label ? ` (${w.label})` : "";
|
|
23
23
|
const storage = w.keyType === "ows" ? " [encrypted]" : " [plaintext]";
|
|
24
|
-
|
|
24
|
+
const chainAddresses = await Promise.all(w.chains.map(async (chainName) => {
|
|
25
|
+
const addr = await getWalletAddress(chainName);
|
|
26
|
+
return `${chainName}: ${addr ?? "unknown"}`;
|
|
27
|
+
}));
|
|
28
|
+
lines.push(` ${w.id}${label}${storage}: ${chainAddresses.join(" | ")}`);
|
|
25
29
|
}
|
|
26
30
|
if (card) {
|
|
27
31
|
lines.push(` Card: ${card.brand} ****${card.last4}`);
|
|
32
|
+
const capabilities = await getCardCapabilities();
|
|
33
|
+
if (capabilities.spt_status === "enabled") {
|
|
34
|
+
lines.push(" Card MPP: ready");
|
|
35
|
+
}
|
|
36
|
+
else if (capabilities.spt_status === "unavailable") {
|
|
37
|
+
lines.push(` Card MPP: unavailable — ${capabilities.message ?? "Stripe Shared Payment Token access is not enabled."}`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
lines.push(` Card MPP: unknown — ${capabilities.message ?? "Could not determine card payment readiness."}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (pendingCardSetupToken) {
|
|
44
|
+
lines.push(" Card setup: pending confirmation");
|
|
45
|
+
}
|
|
46
|
+
if (consumerPrincipal) {
|
|
47
|
+
lines.push("", `Consumer principal: ${consumerPrincipal}`);
|
|
28
48
|
}
|
|
29
49
|
return text(lines.join("\n"));
|
|
30
50
|
});
|
|
@@ -42,7 +62,7 @@ export function registerWalletTools(server) {
|
|
|
42
62
|
.optional()
|
|
43
63
|
.describe("Private key hex string (required for 'import', ignored for 'create')"),
|
|
44
64
|
chain: z.enum(["tempo", "base", "solana"]).optional()
|
|
45
|
-
.describe("Primary chain (default: tempo). Solana
|
|
65
|
+
.describe("Primary chain (default: tempo). Solana uses an OWS wallet plus Stripe deposit-mode USDC."),
|
|
46
66
|
}, async ({ action, name, key, chain }) => {
|
|
47
67
|
// ── Card setup flow ──────────────────────────────────────
|
|
48
68
|
if (action === "add-card") {
|
|
@@ -50,48 +70,32 @@ export function registerWalletTools(server) {
|
|
|
50
70
|
if (existing) {
|
|
51
71
|
return text(`Card already connected: ${existing.brand} ****${existing.last4}\n\nTo replace it, remove the current card first.`);
|
|
52
72
|
}
|
|
73
|
+
const pendingToken = getPendingCardSetupToken();
|
|
53
74
|
// Check if there's a pending setup to complete (user said they're done)
|
|
54
|
-
if (
|
|
55
|
-
const
|
|
56
|
-
const result = await pollCardSetup(pendingToken, 120_000);
|
|
57
|
-
pendingCardSetup = null;
|
|
75
|
+
if (pendingToken) {
|
|
76
|
+
const result = await pollCardSetup(pendingToken, 250);
|
|
58
77
|
if (result) {
|
|
59
|
-
|
|
78
|
+
const principal = await ensureConsumerPrincipal();
|
|
79
|
+
return text(`Connected! ${result.brand} ****${result.last4} is ready for payments.\n\n` +
|
|
80
|
+
`Consumer principal: ${principal}`);
|
|
60
81
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const { qr, url, token } = await initiateCardSetup();
|
|
66
|
-
// Auto-poll for up to 90 seconds — if the user enters their card
|
|
67
|
-
// quickly, the tool returns success without a second call
|
|
68
|
-
const result = await pollCardSetup(token, 90_000);
|
|
69
|
-
if (result) {
|
|
82
|
+
try {
|
|
83
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
84
|
+
const blocks = formatCardSetupBlocks(url);
|
|
70
85
|
return {
|
|
71
|
-
content:
|
|
72
|
-
{ type: "text", text: "\n" + qr.trim() },
|
|
73
|
-
{ type: "text", text: [
|
|
74
|
-
`Open to connect a card: ${url}`,
|
|
75
|
-
"",
|
|
76
|
-
`Connected! ${result.brand} ****${result.last4} is ready for payments.`,
|
|
77
|
-
].join("\n") },
|
|
78
|
-
],
|
|
86
|
+
content: blocks.map((t) => ({ type: "text", text: t })),
|
|
79
87
|
};
|
|
80
88
|
}
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
catch {
|
|
90
|
+
return text("Card setup timed out. Run wallet_setup({ action: \"add-card\" }) to try again.");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Create new card setup session and return immediately so clients
|
|
94
|
+
// don't appear hung while the user is still in the browser flow.
|
|
95
|
+
try {
|
|
96
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
83
97
|
return {
|
|
84
|
-
content:
|
|
85
|
-
{ type: "text", text: "\n" + qr.trim() },
|
|
86
|
-
{ type: "text", text: [
|
|
87
|
-
`IMPORTANT: Present this link to the user to connect a payment card:`,
|
|
88
|
-
"",
|
|
89
|
-
url,
|
|
90
|
-
"",
|
|
91
|
-
`Tell the user: "Scan the QR code above or open this link to connect your card. Let me know when you're done."`,
|
|
92
|
-
`After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
|
|
93
|
-
].join("\n") },
|
|
94
|
-
],
|
|
98
|
+
content: formatCardSetupBlocks(url).map((t) => ({ type: "text", text: t })),
|
|
95
99
|
};
|
|
96
100
|
}
|
|
97
101
|
catch {
|
|
@@ -113,16 +117,20 @@ export function registerWalletTools(server) {
|
|
|
113
117
|
}
|
|
114
118
|
const selectedChains = chain === "solana" ? ["solana"] : ["tempo", "base"];
|
|
115
119
|
const defaultCh = chain ?? "tempo";
|
|
120
|
+
const preferredOwsChain = defaultCh === "solana" ? "solana" : "evm";
|
|
116
121
|
const owsReady = await isOwsAvailable();
|
|
117
122
|
if (action === "create") {
|
|
118
123
|
// Check for existing OWS wallets first
|
|
119
124
|
if (owsReady) {
|
|
120
|
-
const existing =
|
|
125
|
+
const existing = preferredOwsChain === "solana"
|
|
126
|
+
? await listOwsWalletsByChain("solana")
|
|
127
|
+
: await listOwsWallets();
|
|
121
128
|
if (existing.length > 0) {
|
|
122
129
|
const w = existing[0];
|
|
123
130
|
// Check if already in config
|
|
124
131
|
const wallets = getWallets();
|
|
125
|
-
const alreadyLinked = wallets.find(wl => wl.owsWalletId === w.id);
|
|
132
|
+
const alreadyLinked = wallets.find((wl) => wl.owsWalletId === w.id);
|
|
133
|
+
let chainStatus = "Linked to Agent Wonderland.";
|
|
126
134
|
if (!alreadyLinked) {
|
|
127
135
|
addWallet({
|
|
128
136
|
id: w.name,
|
|
@@ -133,14 +141,29 @@ export function registerWalletTools(server) {
|
|
|
133
141
|
label: w.name,
|
|
134
142
|
});
|
|
135
143
|
}
|
|
144
|
+
else {
|
|
145
|
+
const mergedChains = [...new Set([...alreadyLinked.chains, ...selectedChains])];
|
|
146
|
+
if (mergedChains.length !== alreadyLinked.chains.length) {
|
|
147
|
+
addWallet({
|
|
148
|
+
...alreadyLinked,
|
|
149
|
+
chains: mergedChains,
|
|
150
|
+
});
|
|
151
|
+
chainStatus = `Updated linked chains: ${mergedChains.join(", ")}.`;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
chainStatus = "Already linked to Agent Wonderland.";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const principal = await ensureConsumerPrincipal();
|
|
136
158
|
return text([
|
|
137
159
|
"Found existing OWS wallet:",
|
|
138
160
|
` Name: ${w.name}`,
|
|
139
161
|
` Address: ${w.address}`,
|
|
140
162
|
` Chains: ${selectedChains.join(", ")}`,
|
|
141
163
|
` Storage: ~/.ows/ (encrypted)`,
|
|
164
|
+
` Consumer principal: ${principal}`,
|
|
142
165
|
"",
|
|
143
|
-
|
|
166
|
+
chainStatus,
|
|
144
167
|
"",
|
|
145
168
|
`Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
|
|
146
169
|
].join("\n"));
|
|
@@ -149,10 +172,12 @@ export function registerWalletTools(server) {
|
|
|
149
172
|
if (!owsReady) {
|
|
150
173
|
return text("Cannot create wallet: OWS (Open Wallet Standard) is not installed.\n" +
|
|
151
174
|
"Install with: npm install -g @open-wallet-standard/core\n\n" +
|
|
152
|
-
|
|
175
|
+
(defaultCh === "solana"
|
|
176
|
+
? "Solana wallets currently require OWS-managed encrypted storage."
|
|
177
|
+
: "Alternatively, use action 'import' with an existing private key."));
|
|
153
178
|
}
|
|
154
179
|
const walletName = name ?? `aw-${Date.now()}`;
|
|
155
|
-
const result = await createOwsWallet(walletName);
|
|
180
|
+
const result = await createOwsWallet(walletName, preferredOwsChain);
|
|
156
181
|
// Persist to config so payment flows can find it
|
|
157
182
|
addWallet({
|
|
158
183
|
id: walletName,
|
|
@@ -162,6 +187,7 @@ export function registerWalletTools(server) {
|
|
|
162
187
|
defaultChain: defaultCh,
|
|
163
188
|
label: walletName,
|
|
164
189
|
});
|
|
190
|
+
const principal = await ensureConsumerPrincipal();
|
|
165
191
|
return text([
|
|
166
192
|
`Wallet created [encrypted]:`,
|
|
167
193
|
` ID: ${result.walletId}`,
|
|
@@ -169,6 +195,7 @@ export function registerWalletTools(server) {
|
|
|
169
195
|
` Name: ${walletName}`,
|
|
170
196
|
` Chains: ${selectedChains.join(", ")}`,
|
|
171
197
|
` Storage: ~/.ows/ (AES-256-GCM encrypted)`,
|
|
198
|
+
` Consumer principal: ${principal}`,
|
|
172
199
|
"",
|
|
173
200
|
`Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
|
|
174
201
|
"For testnet: npx mppx account fund",
|
|
@@ -177,7 +204,7 @@ export function registerWalletTools(server) {
|
|
|
177
204
|
// action === "import"
|
|
178
205
|
if (owsReady) {
|
|
179
206
|
const walletName = name ?? `imported-${Date.now()}`;
|
|
180
|
-
const result = await importKeyToOws(key, walletName);
|
|
207
|
+
const result = await importKeyToOws(key, walletName, preferredOwsChain);
|
|
181
208
|
addWallet({
|
|
182
209
|
id: walletName,
|
|
183
210
|
keyType: "ows",
|
|
@@ -186,14 +213,20 @@ export function registerWalletTools(server) {
|
|
|
186
213
|
defaultChain: defaultCh,
|
|
187
214
|
label: walletName,
|
|
188
215
|
});
|
|
216
|
+
const principal = await ensureConsumerPrincipal();
|
|
189
217
|
return text([
|
|
190
218
|
`Key imported to OWS [encrypted]:`,
|
|
191
219
|
` ID: ${result.walletId}`,
|
|
192
220
|
` Address: ${result.address}`,
|
|
193
221
|
` Name: ${walletName}`,
|
|
194
222
|
` Chains: ${selectedChains.join(", ")}`,
|
|
223
|
+
` Consumer principal: ${principal}`,
|
|
195
224
|
].join("\n"));
|
|
196
225
|
}
|
|
226
|
+
if (defaultCh === "solana") {
|
|
227
|
+
return text("Solana key import currently requires OWS (Open Wallet Standard) so the ed25519 key can be stored safely.\n" +
|
|
228
|
+
"Install with: npm install -g @open-wallet-standard/core");
|
|
229
|
+
}
|
|
197
230
|
// No OWS — store plaintext
|
|
198
231
|
try {
|
|
199
232
|
const { privateKeyToAccount } = await import("viem/accounts");
|
|
@@ -208,11 +241,13 @@ export function registerWalletTools(server) {
|
|
|
208
241
|
defaultChain: defaultCh,
|
|
209
242
|
label: walletName,
|
|
210
243
|
});
|
|
244
|
+
const principal = await ensureConsumerPrincipal();
|
|
211
245
|
return text([
|
|
212
246
|
`Key imported [plaintext] \u2014 OWS not available for encrypted storage.`,
|
|
213
247
|
` Address: ${account.address}`,
|
|
214
248
|
` Name: ${walletName}`,
|
|
215
249
|
` Chains: ${selectedChains.join(", ")}`,
|
|
250
|
+
` Consumer principal: ${principal}`,
|
|
216
251
|
"",
|
|
217
252
|
"Install OWS for encrypted storage: npm install -g @open-wallet-standard/core",
|
|
218
253
|
].join("\n"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentwonderland/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server for the Agent Wonderland AI agent marketplace",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
26
|
+
"@solana/spl-token": "^0.4.14",
|
|
27
|
+
"@solana/web3.js": "^1.98.4",
|
|
26
28
|
"mppx": "^0.4.9",
|
|
27
29
|
"qrcode": "^1.5.4",
|
|
28
30
|
"viem": "^2.47.6",
|