@agentwonderland/mcp 0.1.46 → 0.1.48
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__/payments.test.js +55 -1
- package/dist/core/__tests__/principal.test.js +18 -0
- package/dist/core/config.d.ts +16 -0
- package/dist/core/config.js +37 -0
- package/dist/core/link-cli.d.ts +27 -0
- package/dist/core/link-cli.js +259 -0
- package/dist/core/mpp-client.d.ts +13 -1
- package/dist/core/mpp-client.js +45 -2
- package/dist/core/payments.js +135 -10
- package/dist/core/principal.js +2 -2
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/index.js +6 -4
- package/dist/tools/__tests__/wallet.test.js +153 -0
- package/dist/tools/passes.js +5 -5
- package/dist/tools/run.js +5 -5
- package/dist/tools/solve.js +5 -5
- package/dist/tools/wallet.js +195 -7
- package/package.json +1 -1
- package/src/core/__tests__/payments.test.ts +78 -1
- package/src/core/__tests__/principal.test.ts +23 -0
- package/src/core/config.ts +56 -0
- package/src/core/link-cli.ts +300 -0
- package/src/core/mpp-client.ts +69 -2
- package/src/core/payments.ts +153 -11
- package/src/core/principal.ts +2 -2
- package/src/core/version.ts +1 -1
- package/src/index.ts +6 -4
- package/src/tools/__tests__/wallet.test.ts +190 -0
- package/src/tools/passes.ts +5 -5
- package/src/tools/run.ts +5 -5
- package/src/tools/solve.ts +5 -5
- package/src/tools/wallet.ts +229 -6
- package/dist/tools/observability.d.ts +0 -2
- package/dist/tools/observability.js +0 -20
package/dist/core/payments.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Users can configure multiple wallets with different chains and select
|
|
10
10
|
* which to use per-request via `--pay-with <wallet-id|chain|card>`.
|
|
11
11
|
*/
|
|
12
|
-
import { getConfig, getWallets, getDefaultWallet, getCardConfig, resolveWalletAndChain, getApiUrl, } from "./config.js";
|
|
12
|
+
import { getConfig, getWallets, getDefaultWallet, getCardConfig, getLinkConfig, resolveWalletAndChain, getApiUrl, } from "./config.js";
|
|
13
13
|
// Feature flag: disable card payment for launch. Flip to true once Stripe
|
|
14
14
|
// approves the live SPT issuer. Config/card state is still persisted so this
|
|
15
15
|
// can be re-enabled without reconfiguring wallets.
|
|
@@ -24,12 +24,30 @@ const REGISTRY_METHOD_MAP = {
|
|
|
24
24
|
base: "base_usdc",
|
|
25
25
|
solana: "solana_usdc",
|
|
26
26
|
card: "stripe_card",
|
|
27
|
+
link: "stripe_card",
|
|
28
|
+
};
|
|
29
|
+
const DEFAULT_LINK_APPROVAL_LIMIT_CENTS = 10_000;
|
|
30
|
+
const ACCEPTED_PAYMENT_ALIASES = {
|
|
31
|
+
tempo: ["tempo_usdc", "tempo"],
|
|
32
|
+
base: ["base_usdc", "base"],
|
|
33
|
+
solana: ["solana_usdc", "solana"],
|
|
34
|
+
card: ["stripe_card", "card"],
|
|
35
|
+
link: ["stripe_card", "card", "link"],
|
|
36
|
+
};
|
|
37
|
+
const DISCOVERY_PAYMENT_ALIASES = {
|
|
38
|
+
tempo: ["tempo_usdc"],
|
|
39
|
+
base: ["base_usdc"],
|
|
40
|
+
solana: ["solana_usdc"],
|
|
41
|
+
card: ["stripe_card", "card"],
|
|
42
|
+
link: ["stripe_card", "card"],
|
|
27
43
|
};
|
|
28
44
|
const METHOD_REGISTRY_MAP = {
|
|
29
45
|
tempo_usdc: "tempo",
|
|
30
46
|
base_usdc: "base",
|
|
31
47
|
solana_usdc: "solana",
|
|
32
48
|
stripe_card: "card",
|
|
49
|
+
card: "card",
|
|
50
|
+
link: "link",
|
|
33
51
|
};
|
|
34
52
|
// ── Helpers ─────────────────────────────────────────────────────
|
|
35
53
|
function normalizeKey(key) {
|
|
@@ -44,6 +62,12 @@ function cardCacheKey() {
|
|
|
44
62
|
return null;
|
|
45
63
|
return `card:${getApiUrl()}:${card.consumerToken}:${card.paymentMethodId ?? ""}`;
|
|
46
64
|
}
|
|
65
|
+
function linkCacheKey() {
|
|
66
|
+
const link = getLinkConfig();
|
|
67
|
+
if (!link)
|
|
68
|
+
return null;
|
|
69
|
+
return `link:${getApiUrl()}:${link.paymentMethodId}`;
|
|
70
|
+
}
|
|
47
71
|
function clearStaleCardCache(activeKey) {
|
|
48
72
|
for (const key of fetchCache.keys()) {
|
|
49
73
|
if (key.startsWith("card:") && key !== activeKey) {
|
|
@@ -51,6 +75,13 @@ function clearStaleCardCache(activeKey) {
|
|
|
51
75
|
}
|
|
52
76
|
}
|
|
53
77
|
}
|
|
78
|
+
function clearStaleLinkCache(activeKey) {
|
|
79
|
+
for (const key of fetchCache.keys()) {
|
|
80
|
+
if (key.startsWith("link:") && key !== activeKey) {
|
|
81
|
+
fetchCache.delete(key);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
54
85
|
// ── Per-protocol initializers ───────────────────────────────────
|
|
55
86
|
async function initEvmMppForChain(wallet, chain) {
|
|
56
87
|
const { Mppx } = await import("./mpp-client.js");
|
|
@@ -127,6 +158,60 @@ async function initCard() {
|
|
|
127
158
|
return null;
|
|
128
159
|
}
|
|
129
160
|
}
|
|
161
|
+
function formatMinorCurrencyAmount(currency, amount) {
|
|
162
|
+
return `${currency.toUpperCase()} ${(Number(amount) / 100).toFixed(2)}`;
|
|
163
|
+
}
|
|
164
|
+
function getLinkApprovalLimitAmount(actualAmount) {
|
|
165
|
+
const actualAmountCents = Number(actualAmount);
|
|
166
|
+
const configuredLimit = Number(process.env.AGENTWONDERLAND_LINK_APPROVAL_LIMIT_CENTS);
|
|
167
|
+
const defaultLimit = Number.isFinite(configuredLimit) && configuredLimit > 0
|
|
168
|
+
? Math.floor(configuredLimit)
|
|
169
|
+
: DEFAULT_LINK_APPROVAL_LIMIT_CENTS;
|
|
170
|
+
return String(Math.max(actualAmountCents, defaultLimit));
|
|
171
|
+
}
|
|
172
|
+
function buildLinkApprovalContext(params) {
|
|
173
|
+
const amountText = formatMinorCurrencyAmount(params.currency, params.amount);
|
|
174
|
+
const approvalAmountText = formatMinorCurrencyAmount(params.currency, params.approvalAmount);
|
|
175
|
+
const agent = params.metadata?.agent_id ? ` Agent ID: ${params.metadata.agent_id}.` : "";
|
|
176
|
+
const job = params.metadata?.job_id ? ` Job ID: ${params.metadata.job_id}.` : "";
|
|
177
|
+
return (`Approve up to ${approvalAmountText} for Agent Wonderland machine payments. ` +
|
|
178
|
+
`This specific user-confirmed agent run is quoted at ${amountText}; the Agent Wonderland gateway will charge the exact quote for this request, not the full approval limit unless the quote itself is that amount.${agent}${job}`);
|
|
179
|
+
}
|
|
180
|
+
async function initLink() {
|
|
181
|
+
const linkConfig = getLinkConfig();
|
|
182
|
+
if (!linkConfig)
|
|
183
|
+
return null;
|
|
184
|
+
try {
|
|
185
|
+
const { Mppx, stripe } = await import("./mpp-client.js");
|
|
186
|
+
const { createLinkSharedPaymentToken } = await import("./link-cli.js");
|
|
187
|
+
const mppx = Mppx.create({
|
|
188
|
+
methods: [stripe({
|
|
189
|
+
paymentMethod: linkConfig.paymentMethodId,
|
|
190
|
+
createToken: async (params) => {
|
|
191
|
+
const approvalAmount = getLinkApprovalLimitAmount(params.amount);
|
|
192
|
+
return createLinkSharedPaymentToken({
|
|
193
|
+
amount: approvalAmount,
|
|
194
|
+
currency: params.currency,
|
|
195
|
+
context: buildLinkApprovalContext({
|
|
196
|
+
amount: params.amount,
|
|
197
|
+
approvalAmount,
|
|
198
|
+
currency: params.currency,
|
|
199
|
+
metadata: params.metadata,
|
|
200
|
+
}),
|
|
201
|
+
expiresAt: params.expiresAt,
|
|
202
|
+
networkId: params.networkId,
|
|
203
|
+
paymentMethodId: linkConfig.paymentMethodId,
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
})],
|
|
207
|
+
polyfill: false,
|
|
208
|
+
});
|
|
209
|
+
return mppx.fetch.bind(mppx);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
130
215
|
/**
|
|
131
216
|
* Initialize a payment-aware fetch for a given wallet + chain.
|
|
132
217
|
*/
|
|
@@ -151,6 +236,21 @@ function initFailureMessage(method, wallet, chain, err) {
|
|
|
151
236
|
* @param method - wallet ID, chain name, or "card". Omit for auto-detection.
|
|
152
237
|
*/
|
|
153
238
|
export async function getPaymentFetch(method) {
|
|
239
|
+
if (method === "link") {
|
|
240
|
+
const ck = linkCacheKey();
|
|
241
|
+
clearStaleLinkCache(ck ?? undefined);
|
|
242
|
+
if (!ck) {
|
|
243
|
+
throw new Error('Payment method "link" is not configured. Run wallet_setup({ action: "add-link" }) after logging into Link.');
|
|
244
|
+
}
|
|
245
|
+
if (fetchCache.has(ck))
|
|
246
|
+
return fetchCache.get(ck);
|
|
247
|
+
const pf = await initLink();
|
|
248
|
+
if (pf) {
|
|
249
|
+
fetchCache.set(ck, pf);
|
|
250
|
+
return pf;
|
|
251
|
+
}
|
|
252
|
+
throw new Error('Payment method "link" failed to initialize. Check Link CLI auth with: npx @stripe/link-cli auth status');
|
|
253
|
+
}
|
|
154
254
|
// Card payment
|
|
155
255
|
if (method === "card") {
|
|
156
256
|
if (!ENABLE_CARD_PAYMENT) {
|
|
@@ -196,6 +296,25 @@ export async function getPaymentFetch(method) {
|
|
|
196
296
|
const configured = getConfiguredMethods();
|
|
197
297
|
const defaultMethod = getConfig().defaultPaymentMethod;
|
|
198
298
|
for (const m of configured) {
|
|
299
|
+
if (m === "link") {
|
|
300
|
+
const ck = linkCacheKey();
|
|
301
|
+
clearStaleLinkCache(ck ?? undefined);
|
|
302
|
+
if (!ck)
|
|
303
|
+
continue;
|
|
304
|
+
if (fetchCache.has(ck))
|
|
305
|
+
return fetchCache.get(ck);
|
|
306
|
+
const pf = await initLink();
|
|
307
|
+
if (pf) {
|
|
308
|
+
fetchCache.set(ck, pf);
|
|
309
|
+
return pf;
|
|
310
|
+
}
|
|
311
|
+
if (m === defaultMethod) {
|
|
312
|
+
const others = configured.filter((x) => x !== m);
|
|
313
|
+
const altText = others.length > 0 ? ` Available alternatives: ${others.join(", ")}` : "";
|
|
314
|
+
throw new Error(`Link payment failed to initialize. Check Link CLI auth with wallet_status.${altText}`);
|
|
315
|
+
}
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
199
318
|
if (m === "card") {
|
|
200
319
|
const ck = cardCacheKey();
|
|
201
320
|
clearStaleCardCache(ck ?? undefined);
|
|
@@ -285,6 +404,9 @@ export function getConfiguredMethods() {
|
|
|
285
404
|
if (ENABLE_CARD_PAYMENT && getCardConfig()) {
|
|
286
405
|
methods.push("card");
|
|
287
406
|
}
|
|
407
|
+
if (getLinkConfig()) {
|
|
408
|
+
methods.push("link");
|
|
409
|
+
}
|
|
288
410
|
// Respect defaultPaymentMethod — move it to front of list (ignore card when disabled)
|
|
289
411
|
const defaultMethod = getConfig().defaultPaymentMethod;
|
|
290
412
|
if (defaultMethod && (defaultMethod !== "card" || ENABLE_CARD_PAYMENT)) {
|
|
@@ -303,6 +425,8 @@ export function getConfiguredMethods() {
|
|
|
303
425
|
export function normalizePaymentMethod(method) {
|
|
304
426
|
if (method === "card")
|
|
305
427
|
return "card";
|
|
428
|
+
if (method === "link")
|
|
429
|
+
return "link";
|
|
306
430
|
const resolved = resolveWalletAndChain(method);
|
|
307
431
|
return resolved?.chain ?? null;
|
|
308
432
|
}
|
|
@@ -315,6 +439,7 @@ export function paymentMethodDisplayName(method) {
|
|
|
315
439
|
case "base": return "Base USDC";
|
|
316
440
|
case "solana": return "Solana USDC";
|
|
317
441
|
case "card": return "Card";
|
|
442
|
+
case "link": return "Link";
|
|
318
443
|
default: return method;
|
|
319
444
|
}
|
|
320
445
|
}
|
|
@@ -325,9 +450,8 @@ export function paymentMethodDisplayName(method) {
|
|
|
325
450
|
*/
|
|
326
451
|
export function getAcceptedPaymentMethods() {
|
|
327
452
|
const methods = getConfiguredMethods();
|
|
328
|
-
return methods
|
|
329
|
-
|
|
330
|
-
.filter(Boolean);
|
|
453
|
+
return [...new Set(methods
|
|
454
|
+
.flatMap((m) => DISCOVERY_PAYMENT_ALIASES[m] ?? [REGISTRY_METHOD_MAP[m]].filter(Boolean)))];
|
|
331
455
|
}
|
|
332
456
|
export function toRegistryPaymentMethod(method) {
|
|
333
457
|
const normalized = normalizePaymentMethod(method);
|
|
@@ -340,16 +464,17 @@ export function getCompatiblePaymentMethods(agent, configuredMethods = getConfig
|
|
|
340
464
|
if (!Array.isArray(acceptedPayments) || acceptedPayments.length === 0) {
|
|
341
465
|
return [...configuredMethods];
|
|
342
466
|
}
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
.filter(Boolean)
|
|
346
|
-
|
|
467
|
+
const acceptedRegistryMethods = new Set(acceptedPayments);
|
|
468
|
+
return configuredMethods.filter((method) => {
|
|
469
|
+
const aliases = ACCEPTED_PAYMENT_ALIASES[method] ?? [REGISTRY_METHOD_MAP[method]].filter(Boolean);
|
|
470
|
+
return aliases.some((alias) => acceptedRegistryMethods.has(alias));
|
|
471
|
+
});
|
|
347
472
|
}
|
|
348
473
|
/**
|
|
349
474
|
* Check whether any payment method is configured.
|
|
350
475
|
*/
|
|
351
476
|
export function hasWalletConfigured() {
|
|
352
|
-
return getWallets().length > 0 || getCardConfig() !== null;
|
|
477
|
+
return getWallets().length > 0 || getCardConfig() !== null || getLinkConfig() !== null;
|
|
353
478
|
}
|
|
354
479
|
/**
|
|
355
480
|
* Get address for a specific method, or the first configured one.
|
|
@@ -357,7 +482,7 @@ export function hasWalletConfigured() {
|
|
|
357
482
|
export async function getWalletAddress(method) {
|
|
358
483
|
let chain;
|
|
359
484
|
let wallet;
|
|
360
|
-
if (method && method !== "card") {
|
|
485
|
+
if (method && method !== "card" && method !== "link") {
|
|
361
486
|
const resolved = resolveWalletAndChain(method);
|
|
362
487
|
wallet = resolved?.wallet;
|
|
363
488
|
chain = resolved?.chain;
|
package/dist/core/principal.js
CHANGED
|
@@ -106,7 +106,7 @@ export async function getBaseRebatePrincipal() {
|
|
|
106
106
|
return (await principalForChain("base")) ?? principalForChain("tempo");
|
|
107
107
|
}
|
|
108
108
|
export async function getConsumerPrincipalForMethod(method) {
|
|
109
|
-
if (!method || method === "card") {
|
|
109
|
+
if (!method || method === "card" || method === "link") {
|
|
110
110
|
return getConsumerPrincipal();
|
|
111
111
|
}
|
|
112
112
|
const resolved = resolveWalletAndChain(method);
|
|
@@ -129,7 +129,7 @@ export async function ensureConsumerPrincipalForMethod(method) {
|
|
|
129
129
|
const existing = await getConsumerPrincipalForMethod(method);
|
|
130
130
|
if (existing)
|
|
131
131
|
return existing;
|
|
132
|
-
if (method && method !== "card") {
|
|
132
|
+
if (method && method !== "card" && method !== "link") {
|
|
133
133
|
throw new Error(`Could not derive a consumer principal for payment method "${method}". ` +
|
|
134
134
|
"Check wallet_status and confirm that chain is configured for the active wallet.");
|
|
135
135
|
}
|
package/dist/core/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MCP_PACKAGE_VERSION = "0.1.
|
|
1
|
+
export declare const MCP_PACKAGE_VERSION = "0.1.48";
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const MCP_PACKAGE_VERSION = "0.1.
|
|
1
|
+
export const MCP_PACKAGE_VERSION = "0.1.48";
|
package/dist/index.js
CHANGED
|
@@ -44,11 +44,12 @@ export async function startMcpServer() {
|
|
|
44
44
|
"3. After a successful run, rate_agent() and optionally tip_agent() if the result was useful.",
|
|
45
45
|
"4. Use list_jobs() to recover state across sessions (it checks every configured wallet).",
|
|
46
46
|
"",
|
|
47
|
-
"PAYMENT
|
|
48
|
-
"- Supported
|
|
47
|
+
"PAYMENT:",
|
|
48
|
+
"- Supported payment methods: Link card/bank via @stripe/link-cli, Tempo USDC, Base USDC, and Solana USDC.",
|
|
49
|
+
"- Link payments may ask the user to approve a spend request in Link before the first charge.",
|
|
49
50
|
"- Tempo and Base share one EVM wallet key. Solana uses a separate ed25519 key. One OWS wallet can manage both.",
|
|
50
51
|
"- If pay_with is omitted, the MCP auto-selects a compatible configured rail. Pass pay_with explicitly",
|
|
51
|
-
" (tempo | base | solana | wallet-id) for deterministic behavior.",
|
|
52
|
+
" (tempo | base | solana | link | wallet-id) for deterministic behavior.",
|
|
52
53
|
"- Payment is automatic: on a 402 challenge the MCP signs on-chain, submits, then retries. Failed runs are refunded.",
|
|
53
54
|
"- If a specific rail fails, surface the real reason — do NOT silently retry with a different method.",
|
|
54
55
|
"- Headless/automation: set wallet_set_policy() to cap max_per_tx and max_per_day so a runaway loop can't drain funds.",
|
|
@@ -63,7 +64,8 @@ export async function startMcpServer() {
|
|
|
63
64
|
"",
|
|
64
65
|
"WALLET HYGIENE:",
|
|
65
66
|
"- wallet_status shows per-chain USDC balance and the active network (mainnet vs testnet).",
|
|
66
|
-
"- To
|
|
67
|
+
"- To set up payments: wallet_setup({ action: \"start\" }). Link card/bank is recommended for most users.",
|
|
68
|
+
"- To create or import a crypto wallet directly: wallet_setup({ action: \"create\" }) or { action: \"import\", key }.",
|
|
67
69
|
"- NEVER delete or rotate keys programmatically. Direct users to edit ~/.agentwonderland/config.json or ~/.ows/ manually.",
|
|
68
70
|
].join("\n"),
|
|
69
71
|
});
|
|
@@ -3,8 +3,11 @@ const state = vi.hoisted(() => ({
|
|
|
3
3
|
wallets: [],
|
|
4
4
|
addedWallets: [],
|
|
5
5
|
card: null,
|
|
6
|
+
link: null,
|
|
7
|
+
pendingLinkSetup: null,
|
|
6
8
|
pendingCardSetupToken: null,
|
|
7
9
|
spendPolicies: {},
|
|
10
|
+
defaultPaymentMethod: undefined,
|
|
8
11
|
consumerPrincipal: "did:pkh:eip155:8453:0xabc",
|
|
9
12
|
owsAvailable: true,
|
|
10
13
|
owsWallets: [],
|
|
@@ -30,6 +33,12 @@ const state = vi.hoisted(() => ({
|
|
|
30
33
|
pollCardSetupCalls: [],
|
|
31
34
|
pollCardSetupResult: null,
|
|
32
35
|
setCardConfigCalls: [],
|
|
36
|
+
setLinkConfigCalls: [],
|
|
37
|
+
setPendingLinkSetupCalls: [],
|
|
38
|
+
linkAuthenticated: false,
|
|
39
|
+
linkPaymentMethods: [],
|
|
40
|
+
linkLoginCalls: 0,
|
|
41
|
+
linkPaymentMethodAddCalls: 0,
|
|
33
42
|
}));
|
|
34
43
|
vi.mock("../../core/config.js", () => ({
|
|
35
44
|
addWallet: (wallet) => {
|
|
@@ -43,6 +52,8 @@ vi.mock("../../core/config.js", () => ({
|
|
|
43
52
|
}
|
|
44
53
|
},
|
|
45
54
|
getCardConfig: () => state.card,
|
|
55
|
+
getLinkConfig: () => state.link,
|
|
56
|
+
getPendingLinkSetup: () => state.pendingLinkSetup,
|
|
46
57
|
getPendingCardSetupToken: () => state.pendingCardSetupToken,
|
|
47
58
|
getSpendPolicy: (walletId) => state.spendPolicies[walletId] ?? null,
|
|
48
59
|
getWallets: () => state.wallets,
|
|
@@ -50,6 +61,17 @@ vi.mock("../../core/config.js", () => ({
|
|
|
50
61
|
state.card = card;
|
|
51
62
|
state.setCardConfigCalls.push(card);
|
|
52
63
|
},
|
|
64
|
+
setDefaultPaymentMethod: (method) => {
|
|
65
|
+
state.defaultPaymentMethod = method;
|
|
66
|
+
},
|
|
67
|
+
setLinkConfig: (link) => {
|
|
68
|
+
state.link = link;
|
|
69
|
+
state.setLinkConfigCalls.push(link);
|
|
70
|
+
},
|
|
71
|
+
setPendingLinkSetup: (pending) => {
|
|
72
|
+
state.pendingLinkSetup = pending;
|
|
73
|
+
state.setPendingLinkSetupCalls.push(pending);
|
|
74
|
+
},
|
|
53
75
|
setSpendPolicy: (walletId, policy) => {
|
|
54
76
|
state.spendPolicies[walletId] = policy;
|
|
55
77
|
},
|
|
@@ -69,6 +91,22 @@ vi.mock("../../core/card-setup.js", () => ({
|
|
|
69
91
|
return state.pollCardSetupResult;
|
|
70
92
|
},
|
|
71
93
|
}));
|
|
94
|
+
vi.mock("../../core/link-cli.js", () => ({
|
|
95
|
+
getLinkCliAuthStatus: async () => ({
|
|
96
|
+
authenticated: state.linkAuthenticated,
|
|
97
|
+
}),
|
|
98
|
+
listLinkPaymentMethods: async () => state.linkPaymentMethods,
|
|
99
|
+
openLinkPaymentMethodAdd: async () => {
|
|
100
|
+
state.linkPaymentMethodAddCalls++;
|
|
101
|
+
},
|
|
102
|
+
startLinkCliLogin: async () => {
|
|
103
|
+
state.linkLoginCalls++;
|
|
104
|
+
return {
|
|
105
|
+
verificationUrl: "https://app.link.com/device/setup?code=test-link-code",
|
|
106
|
+
phrase: "test-link-code",
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
}));
|
|
72
110
|
vi.mock("../../core/ows-adapter.js", () => ({
|
|
73
111
|
createOwsWallet: async (name, chain) => {
|
|
74
112
|
state.createdWalletCalls.push({ name, chain });
|
|
@@ -90,8 +128,11 @@ function resetState() {
|
|
|
90
128
|
state.wallets = [];
|
|
91
129
|
state.addedWallets = [];
|
|
92
130
|
state.card = null;
|
|
131
|
+
state.link = null;
|
|
132
|
+
state.pendingLinkSetup = null;
|
|
93
133
|
state.pendingCardSetupToken = null;
|
|
94
134
|
state.spendPolicies = {};
|
|
135
|
+
state.defaultPaymentMethod = undefined;
|
|
95
136
|
state.consumerPrincipal = "did:pkh:eip155:8453:0xabc";
|
|
96
137
|
state.owsAvailable = true;
|
|
97
138
|
state.owsWallets = [];
|
|
@@ -117,6 +158,12 @@ function resetState() {
|
|
|
117
158
|
state.pollCardSetupCalls = [];
|
|
118
159
|
state.pollCardSetupResult = null;
|
|
119
160
|
state.setCardConfigCalls = [];
|
|
161
|
+
state.setLinkConfigCalls = [];
|
|
162
|
+
state.setPendingLinkSetupCalls = [];
|
|
163
|
+
state.linkAuthenticated = false;
|
|
164
|
+
state.linkPaymentMethods = [];
|
|
165
|
+
state.linkLoginCalls = 0;
|
|
166
|
+
state.linkPaymentMethodAddCalls = 0;
|
|
120
167
|
}
|
|
121
168
|
async function getWalletSetupTool() {
|
|
122
169
|
const tools = new Map();
|
|
@@ -158,11 +205,40 @@ describe("wallet_setup tool", () => {
|
|
|
158
205
|
label: "launch-wallet",
|
|
159
206
|
},
|
|
160
207
|
]);
|
|
208
|
+
expect(state.defaultPaymentMethod).toBe("base");
|
|
161
209
|
expect(text).toContain("Wallet created [encrypted]:");
|
|
162
210
|
expect(text).toContain("Address: 0x1111111111111111111111111111111111111111");
|
|
163
211
|
expect(text).toContain("Chains: tempo, base");
|
|
164
212
|
expect(text).toContain("Consumer principal: did:pkh:eip155:8453:0xabc");
|
|
165
213
|
});
|
|
214
|
+
it("shows a guided payment setup menu", async () => {
|
|
215
|
+
const walletSetup = await getWalletSetupTool();
|
|
216
|
+
const result = await walletSetup({ action: "start" });
|
|
217
|
+
const text = flattenText(result);
|
|
218
|
+
expect(text).toContain("Choose a payment method to set up:");
|
|
219
|
+
expect(text).toContain("Link card or bank account (recommended)");
|
|
220
|
+
expect(text).toContain('wallet_setup({ action: "add-link" })');
|
|
221
|
+
expect(text).toContain('wallet_setup({ action: "create", chain: "tempo" })');
|
|
222
|
+
expect(text).toContain('wallet_setup({ action: "create", chain: "base" })');
|
|
223
|
+
expect(text).toContain('wallet_setup({ action: "create", chain: "solana" })');
|
|
224
|
+
});
|
|
225
|
+
it("shows the setup menu when no payment methods are configured", async () => {
|
|
226
|
+
const tools = new Map();
|
|
227
|
+
const server = {
|
|
228
|
+
tool: (name, _description, _schema, handler) => {
|
|
229
|
+
tools.set(name, handler);
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
const { registerWalletTools } = await import("../wallet.js");
|
|
233
|
+
registerWalletTools(server);
|
|
234
|
+
const walletStatus = tools.get("wallet_status");
|
|
235
|
+
if (!walletStatus)
|
|
236
|
+
throw new Error("wallet_status tool was not registered");
|
|
237
|
+
const result = await walletStatus({});
|
|
238
|
+
const text = flattenText(result);
|
|
239
|
+
expect(text).toContain("No payment methods configured.");
|
|
240
|
+
expect(text).toContain("Link card or bank account (recommended)");
|
|
241
|
+
});
|
|
166
242
|
it("imports a wallet into OWS encrypted storage with Base as the default chain", async () => {
|
|
167
243
|
const walletSetup = await getWalletSetupTool();
|
|
168
244
|
const result = await walletSetup({
|
|
@@ -185,6 +261,7 @@ describe("wallet_setup tool", () => {
|
|
|
185
261
|
label: "imported-wallet",
|
|
186
262
|
},
|
|
187
263
|
]);
|
|
264
|
+
expect(state.defaultPaymentMethod).toBe("base");
|
|
188
265
|
expect(text).toContain("Key imported to OWS [encrypted]:");
|
|
189
266
|
expect(text).toContain("Address: 0x2222222222222222222222222222222222222222");
|
|
190
267
|
expect(text).toContain("Consumer principal: did:pkh:eip155:8453:0xabc");
|
|
@@ -228,4 +305,80 @@ describe("wallet_setup tool", () => {
|
|
|
228
305
|
expect(text).toContain("Removed Visa ****4242.");
|
|
229
306
|
expect(text).toContain("Card disconnected from Agent Wonderland.");
|
|
230
307
|
});
|
|
308
|
+
it("starts Link device auth from wallet_setup", async () => {
|
|
309
|
+
const walletSetup = await getWalletSetupTool();
|
|
310
|
+
const result = await walletSetup({ action: "add-link" });
|
|
311
|
+
const text = flattenText(result);
|
|
312
|
+
expect(state.linkLoginCalls).toBe(1);
|
|
313
|
+
expect(state.setPendingLinkSetupCalls[0]).toMatchObject({
|
|
314
|
+
verificationUrl: "https://app.link.com/device/setup?code=test-link-code",
|
|
315
|
+
phrase: "test-link-code",
|
|
316
|
+
});
|
|
317
|
+
expect(text).toContain("Approve Agent Wonderland in Link:");
|
|
318
|
+
expect(text).toContain("https://app.link.com/device/setup?code=test-link-code");
|
|
319
|
+
expect(text).toContain("Verification phrase: test-link-code");
|
|
320
|
+
});
|
|
321
|
+
it("continues a pending Link auth without starting a new login", async () => {
|
|
322
|
+
state.pendingLinkSetup = {
|
|
323
|
+
verificationUrl: "https://app.link.com/device/setup?code=existing",
|
|
324
|
+
phrase: "existing",
|
|
325
|
+
createdAt: new Date().toISOString(),
|
|
326
|
+
};
|
|
327
|
+
const walletSetup = await getWalletSetupTool();
|
|
328
|
+
const result = await walletSetup({ action: "add-link" });
|
|
329
|
+
const text = flattenText(result);
|
|
330
|
+
expect(state.linkLoginCalls).toBe(0);
|
|
331
|
+
expect(text).toContain("https://app.link.com/device/setup?code=existing");
|
|
332
|
+
expect(text).toContain("Verification phrase: existing");
|
|
333
|
+
});
|
|
334
|
+
it("connects the only Link payment method after auth", async () => {
|
|
335
|
+
state.linkAuthenticated = true;
|
|
336
|
+
state.pendingLinkSetup = {
|
|
337
|
+
verificationUrl: "https://app.link.com/device/setup?code=existing",
|
|
338
|
+
phrase: "existing",
|
|
339
|
+
createdAt: new Date().toISOString(),
|
|
340
|
+
};
|
|
341
|
+
state.linkPaymentMethods = [
|
|
342
|
+
{ id: "csmrpd_123", label: "Barclays Mastercard ****3688" },
|
|
343
|
+
];
|
|
344
|
+
const walletSetup = await getWalletSetupTool();
|
|
345
|
+
const result = await walletSetup({ action: "add-link" });
|
|
346
|
+
const text = flattenText(result);
|
|
347
|
+
expect(state.setPendingLinkSetupCalls).toEqual([null]);
|
|
348
|
+
expect(state.setLinkConfigCalls).toEqual([
|
|
349
|
+
{ paymentMethodId: "csmrpd_123", label: "Barclays Mastercard ****3688" },
|
|
350
|
+
]);
|
|
351
|
+
expect(text).toContain("Link payment method connected.");
|
|
352
|
+
expect(text).toContain("Barclays Mastercard ****3688");
|
|
353
|
+
});
|
|
354
|
+
it("shows friendly names when multiple Link payment methods exist", async () => {
|
|
355
|
+
state.linkAuthenticated = true;
|
|
356
|
+
state.linkPaymentMethods = [
|
|
357
|
+
{ id: "csmrpd_1", label: "Barclays Arrival Mastercard ****3688" },
|
|
358
|
+
{ id: "csmrpd_2", label: "Total Checking bank ****2889" },
|
|
359
|
+
];
|
|
360
|
+
const walletSetup = await getWalletSetupTool();
|
|
361
|
+
const result = await walletSetup({ action: "add-link" });
|
|
362
|
+
const text = flattenText(result);
|
|
363
|
+
expect(text).toContain("Barclays Arrival Mastercard ****3688 (csmrpd_1)");
|
|
364
|
+
expect(text).toContain("Total Checking bank ****2889 (csmrpd_2)");
|
|
365
|
+
expect(text).toContain('link_payment_method: "<name or last4>"');
|
|
366
|
+
});
|
|
367
|
+
it("resolves a friendly Link payment method selector", async () => {
|
|
368
|
+
state.linkAuthenticated = true;
|
|
369
|
+
state.linkPaymentMethods = [
|
|
370
|
+
{ id: "csmrpd_1", label: "Barclays Arrival Mastercard ****3688", searchText: "csmrpd_1 barclays arrival mastercard 3688" },
|
|
371
|
+
{ id: "csmrpd_2", label: "Total Checking bank ****2889", searchText: "csmrpd_2 total checking bank 2889" },
|
|
372
|
+
];
|
|
373
|
+
const walletSetup = await getWalletSetupTool();
|
|
374
|
+
const result = await walletSetup({
|
|
375
|
+
action: "add-link",
|
|
376
|
+
link_payment_method: "3688",
|
|
377
|
+
});
|
|
378
|
+
const text = flattenText(result);
|
|
379
|
+
expect(state.setLinkConfigCalls).toEqual([
|
|
380
|
+
{ paymentMethodId: "csmrpd_1", label: "Barclays Arrival Mastercard ****3688" },
|
|
381
|
+
]);
|
|
382
|
+
expect(text).toContain("Barclays Arrival Mastercard ****3688 (csmrpd_1)");
|
|
383
|
+
});
|
|
231
384
|
});
|
package/dist/tools/passes.js
CHANGED
|
@@ -35,7 +35,7 @@ export function registerPassTools(server) {
|
|
|
35
35
|
server.tool("buy_agent_credit_pack", "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.", {
|
|
36
36
|
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
37
37
|
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."),
|
|
38
|
-
pay_with: z.string().optional().describe("Payment method — wallet ID, chain name, or 'card'. Auto-detected if omitted."),
|
|
38
|
+
pay_with: z.string().optional().describe("Payment method — wallet ID, chain name, 'link', or 'card'. Auto-detected if omitted."),
|
|
39
39
|
confirmed: z.boolean().optional().describe("Set to true to confirm the purchase after seeing the quote."),
|
|
40
40
|
}, async ({ agent_id, pack_id, pay_with, confirmed }) => {
|
|
41
41
|
if (!hasWalletConfigured()) {
|
|
@@ -51,10 +51,10 @@ export function registerPassTools(server) {
|
|
|
51
51
|
const setupLines = [
|
|
52
52
|
"No payment method configured.",
|
|
53
53
|
"",
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
54
|
+
"Recommended: wallet_setup({ action: \"add-link\" }) to connect a Link card or bank account.",
|
|
55
|
+
"You can also run wallet_setup({ action: \"start\" }) to see all payment options.",
|
|
56
|
+
"",
|
|
57
|
+
"USDC options: Tempo, Base, or Solana.",
|
|
58
58
|
];
|
|
59
59
|
if (isCardPaymentEnabled()) {
|
|
60
60
|
setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
|
package/dist/tools/run.js
CHANGED
|
@@ -88,7 +88,7 @@ export function registerRunTools(server) {
|
|
|
88
88
|
server.tool("run_agent", "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. If a file you need isn't on this MCP server's filesystem (e.g. a sandboxed /mnt/... attachment), call upload_file first to get a presigned upload URL, PUT the bytes to it, then pass the returned GET URL as input instead — that keeps the bytes out of the conversation context.", {
|
|
89
89
|
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
90
90
|
input: z.record(z.string(), z.unknown()).describe("Input payload for the agent"),
|
|
91
|
-
pay_with: z.string().trim().min(1).optional().describe("Payment method — wallet ID, chain name (tempo, base,
|
|
91
|
+
pay_with: z.string().trim().min(1).optional().describe("Payment method — wallet ID, chain name (tempo, base, solana), 'link', or 'card'. Auto-detected if omitted."),
|
|
92
92
|
confirmed: z.boolean().optional().describe("Set to true to confirm spending after seeing the price quote."),
|
|
93
93
|
}, async ({ agent_id, input, pay_with, confirmed }) => {
|
|
94
94
|
if (!hasWalletConfigured()) {
|
|
@@ -104,10 +104,10 @@ export function registerRunTools(server) {
|
|
|
104
104
|
const setupLines = [
|
|
105
105
|
"No payment method configured.",
|
|
106
106
|
"",
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
107
|
+
"Recommended: wallet_setup({ action: \"add-link\" }) to connect a Link card or bank account.",
|
|
108
|
+
"You can also run wallet_setup({ action: \"start\" }) to see all payment options.",
|
|
109
|
+
"",
|
|
110
|
+
"USDC options: Tempo, Base, or Solana.",
|
|
111
111
|
];
|
|
112
112
|
if (isCardPaymentEnabled()) {
|
|
113
113
|
setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
|
package/dist/tools/solve.js
CHANGED
|
@@ -126,7 +126,7 @@ export function registerSolveTools(server) {
|
|
|
126
126
|
.trim()
|
|
127
127
|
.min(1)
|
|
128
128
|
.optional()
|
|
129
|
-
.describe("Payment method — wallet ID, chain name (tempo, base,
|
|
129
|
+
.describe("Payment method — wallet ID, chain name (tempo, base, solana), 'link', or 'card'. Auto-detected if omitted."),
|
|
130
130
|
confirmed: z
|
|
131
131
|
.boolean()
|
|
132
132
|
.optional()
|
|
@@ -145,10 +145,10 @@ export function registerSolveTools(server) {
|
|
|
145
145
|
const setupLines = [
|
|
146
146
|
"No payment method configured.",
|
|
147
147
|
"",
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
148
|
+
"Recommended: wallet_setup({ action: \"add-link\" }) to connect a Link card or bank account.",
|
|
149
|
+
"You can also run wallet_setup({ action: \"start\" }) to see all payment options.",
|
|
150
|
+
"",
|
|
151
|
+
"USDC options: Tempo, Base, or Solana.",
|
|
152
152
|
];
|
|
153
153
|
if (isCardPaymentEnabled()) {
|
|
154
154
|
setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
|