@agentwonderland/mcp 0.1.24 → 0.1.25
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__/amount-utils.test.d.ts +1 -0
- package/dist/core/__tests__/amount-utils.test.js +11 -0
- package/dist/core/__tests__/payments.test.js +55 -6
- package/dist/core/__tests__/spend-policy.test.d.ts +1 -0
- package/dist/core/__tests__/spend-policy.test.js +40 -0
- package/dist/core/amount-utils.d.ts +1 -0
- package/dist/core/amount-utils.js +4 -0
- package/dist/core/base-charge.js +3 -2
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.js +22 -0
- package/dist/core/formatters.d.ts +2 -3
- package/dist/core/formatters.js +5 -7
- package/dist/core/payments.js +14 -4
- package/dist/core/solana-charge.js +2 -1
- package/dist/core/spend-policy.d.ts +12 -0
- package/dist/core/spend-policy.js +53 -0
- package/dist/core/types.d.ts +1 -2
- package/dist/index.js +5 -2
- package/dist/prompts/index.js +4 -2
- package/dist/resources/agents.js +1 -1
- package/dist/tools/agent-info.js +2 -2
- package/dist/tools/favorites.js +1 -1
- package/dist/tools/observability.d.ts +2 -0
- package/dist/tools/observability.js +20 -0
- package/dist/tools/run.js +40 -28
- package/dist/tools/solve.js +45 -39
- package/dist/tools/wallet.js +26 -10
- package/package.json +1 -1
- package/src/core/__tests__/amount-utils.test.ts +13 -0
- package/src/core/__tests__/payments.test.ts +68 -6
- package/src/core/__tests__/spend-policy.test.ts +58 -0
- package/src/core/amount-utils.ts +5 -0
- package/src/core/base-charge.ts +3 -2
- package/src/core/config.ts +45 -0
- package/src/core/formatters.ts +6 -8
- package/src/core/payments.ts +17 -4
- package/src/core/solana-charge.ts +2 -1
- package/src/core/spend-policy.ts +69 -0
- package/src/core/types.ts +1 -2
- package/src/index.ts +5 -2
- package/src/prompts/index.ts +4 -2
- package/src/resources/agents.ts +1 -1
- package/src/tools/agent-info.ts +2 -2
- package/src/tools/favorites.ts +1 -4
- package/src/tools/observability.ts +43 -0
- package/src/tools/passes.ts +1 -7
- package/src/tools/run.ts +44 -43
- package/src/tools/solve.ts +50 -55
- package/src/tools/wallet.ts +30 -10
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { toAtomicAmount } from "../amount-utils.js";
|
|
3
|
+
describe("toAtomicAmount", () => {
|
|
4
|
+
it("converts decimal USDC challenge amounts into atomic units", () => {
|
|
5
|
+
expect(toAtomicAmount("0.020000")).toBe(20000n);
|
|
6
|
+
expect(toAtomicAmount("1.000000")).toBe(1000000n);
|
|
7
|
+
});
|
|
8
|
+
it("supports different decimal precisions", () => {
|
|
9
|
+
expect(toAtomicAmount("0.50", 2)).toBe(50n);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -1,40 +1,55 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const TEST_KEY = "1111111111111111111111111111111111111111111111111111111111111111";
|
|
2
3
|
let currentCard = {
|
|
3
4
|
consumerToken: "consumer_one",
|
|
4
5
|
paymentMethodId: "pm_one",
|
|
5
6
|
last4: "1111",
|
|
6
7
|
brand: "visa",
|
|
7
8
|
};
|
|
8
|
-
|
|
9
|
+
let currentDefaultWallet;
|
|
10
|
+
let currentWallets = [];
|
|
11
|
+
let currentResolvedMethod = null;
|
|
12
|
+
const createdFetches = [vi.fn(), vi.fn(), vi.fn(), vi.fn()];
|
|
9
13
|
const mockMppxCreate = vi.fn();
|
|
10
14
|
const mockStripe = vi.fn((opts) => opts);
|
|
15
|
+
const mockTempo = vi.fn((..._args) => "tempo_method");
|
|
16
|
+
const mockBaseChargeClient = vi.fn((..._args) => "base_method");
|
|
11
17
|
vi.mock("../config.js", () => ({
|
|
12
18
|
getApiUrl: () => "http://api.test",
|
|
13
19
|
getCardConfig: () => currentCard,
|
|
14
20
|
getConfig: () => ({ defaultPaymentMethod: "card" }),
|
|
15
|
-
getDefaultWallet: () =>
|
|
16
|
-
getWallets: () =>
|
|
17
|
-
resolveWalletAndChain: () =>
|
|
21
|
+
getDefaultWallet: () => currentDefaultWallet,
|
|
22
|
+
getWallets: () => currentWallets,
|
|
23
|
+
resolveWalletAndChain: () => currentResolvedMethod,
|
|
18
24
|
}));
|
|
19
25
|
vi.mock("mppx/client", () => ({
|
|
20
26
|
Mppx: {
|
|
21
27
|
create: (config) => mockMppxCreate(config),
|
|
22
28
|
},
|
|
23
29
|
stripe: (config) => mockStripe(config),
|
|
30
|
+
tempo: (config) => mockTempo(config),
|
|
24
31
|
}));
|
|
25
|
-
|
|
32
|
+
vi.mock("../base-charge.js", () => ({
|
|
33
|
+
baseChargeClient: (config) => mockBaseChargeClient(config),
|
|
34
|
+
}));
|
|
35
|
+
describe("payment method initialization", () => {
|
|
26
36
|
beforeEach(() => {
|
|
27
37
|
vi.clearAllMocks();
|
|
38
|
+
vi.resetModules();
|
|
28
39
|
currentCard = {
|
|
29
40
|
consumerToken: "consumer_one",
|
|
30
41
|
paymentMethodId: "pm_one",
|
|
31
42
|
last4: "1111",
|
|
32
43
|
brand: "visa",
|
|
33
44
|
};
|
|
45
|
+
currentDefaultWallet = undefined;
|
|
46
|
+
currentWallets = [];
|
|
47
|
+
currentResolvedMethod = null;
|
|
34
48
|
mockMppxCreate
|
|
35
49
|
.mockReturnValueOnce({ fetch: createdFetches[0] })
|
|
36
50
|
.mockReturnValueOnce({ fetch: createdFetches[1] })
|
|
37
|
-
.mockReturnValueOnce({ fetch: createdFetches[2] })
|
|
51
|
+
.mockReturnValueOnce({ fetch: createdFetches[2] })
|
|
52
|
+
.mockReturnValueOnce({ fetch: createdFetches[3] });
|
|
38
53
|
});
|
|
39
54
|
it("rebuilds the cached card fetch when the card config changes", async () => {
|
|
40
55
|
const { getPaymentFetch } = await import("../payments.js");
|
|
@@ -49,4 +64,38 @@ describe("card payment fetch cache", () => {
|
|
|
49
64
|
expect(firstFetch).not.toBe(secondFetch);
|
|
50
65
|
expect(mockMppxCreate).toHaveBeenCalledTimes(2);
|
|
51
66
|
});
|
|
67
|
+
it("initializes only the Base method when base is requested", async () => {
|
|
68
|
+
const wallet = {
|
|
69
|
+
id: "aw-main",
|
|
70
|
+
keyType: "raw",
|
|
71
|
+
key: TEST_KEY,
|
|
72
|
+
chains: ["tempo", "base"],
|
|
73
|
+
defaultChain: "base",
|
|
74
|
+
};
|
|
75
|
+
currentDefaultWallet = wallet;
|
|
76
|
+
currentWallets = [wallet];
|
|
77
|
+
currentResolvedMethod = { wallet, chain: "base" };
|
|
78
|
+
const { getPaymentFetch } = await import("../payments.js");
|
|
79
|
+
await getPaymentFetch("base");
|
|
80
|
+
expect(mockBaseChargeClient).toHaveBeenCalledTimes(1);
|
|
81
|
+
expect(mockTempo).not.toHaveBeenCalled();
|
|
82
|
+
expect(mockMppxCreate).toHaveBeenCalledWith(expect.objectContaining({ methods: ["base_method"] }));
|
|
83
|
+
});
|
|
84
|
+
it("initializes only the Tempo method when tempo is requested", async () => {
|
|
85
|
+
const wallet = {
|
|
86
|
+
id: "aw-main",
|
|
87
|
+
keyType: "raw",
|
|
88
|
+
key: TEST_KEY,
|
|
89
|
+
chains: ["tempo", "base"],
|
|
90
|
+
defaultChain: "tempo",
|
|
91
|
+
};
|
|
92
|
+
currentDefaultWallet = wallet;
|
|
93
|
+
currentWallets = [wallet];
|
|
94
|
+
currentResolvedMethod = { wallet, chain: "tempo" };
|
|
95
|
+
const { getPaymentFetch } = await import("../payments.js");
|
|
96
|
+
await getPaymentFetch("tempo");
|
|
97
|
+
expect(mockTempo).toHaveBeenCalledTimes(1);
|
|
98
|
+
expect(mockBaseChargeClient).not.toHaveBeenCalled();
|
|
99
|
+
expect(mockMppxCreate).toHaveBeenCalledWith(expect.objectContaining({ methods: ["tempo_method"] }));
|
|
100
|
+
});
|
|
52
101
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const state = {
|
|
3
|
+
policies: {},
|
|
4
|
+
ledger: [],
|
|
5
|
+
};
|
|
6
|
+
vi.mock("../config.js", () => ({
|
|
7
|
+
getSpendPolicy: (method) => state.policies[method] ?? null,
|
|
8
|
+
getSpendLedger: () => state.ledger,
|
|
9
|
+
saveSpendLedger: (entries) => {
|
|
10
|
+
state.ledger = entries;
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
import { canSpend, getDailySpend, recordSpend, requiresPolicyConfirmation, } from "../spend-policy.js";
|
|
14
|
+
describe("spend-policy", () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
state.policies = {};
|
|
17
|
+
state.ledger = [];
|
|
18
|
+
});
|
|
19
|
+
it("allows spend when no policy is set", () => {
|
|
20
|
+
expect(canSpend({ method: "wallet-1", amountUsd: 5 })).toEqual({ ok: true });
|
|
21
|
+
});
|
|
22
|
+
it("blocks spends above max_per_tx", () => {
|
|
23
|
+
state.policies["wallet-1"] = { maxPerTxUsd: 2 };
|
|
24
|
+
const result = canSpend({ method: "wallet-1", amountUsd: 3 });
|
|
25
|
+
expect(result.ok).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
it("tracks daily spend totals per method", () => {
|
|
28
|
+
const now = new Date("2026-04-08T12:00:00.000Z");
|
|
29
|
+
recordSpend("wallet-1", 1.25, now);
|
|
30
|
+
recordSpend("wallet-1", 0.75, now);
|
|
31
|
+
recordSpend("wallet-2", 10, now);
|
|
32
|
+
expect(getDailySpend("wallet-1", now)).toBeCloseTo(2);
|
|
33
|
+
expect(getDailySpend("wallet-2", now)).toBeCloseTo(10);
|
|
34
|
+
});
|
|
35
|
+
it("requires explicit confirmation above configured threshold", () => {
|
|
36
|
+
state.policies["wallet-1"] = { requireConfirmationAboveUsd: 2 };
|
|
37
|
+
expect(requiresPolicyConfirmation("wallet-1", 1.99)).toBe(false);
|
|
38
|
+
expect(requiresPolicyConfirmation("wallet-1", 2)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function toAtomicAmount(amount: string, decimals?: number): bigint;
|
package/dist/core/base-charge.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* the tx hash as a credential. Plugs into mppx's compose/dispatch system.
|
|
6
6
|
*/
|
|
7
7
|
import { Method, Credential, z } from "mppx";
|
|
8
|
+
import { toAtomicAmount } from "./amount-utils.js";
|
|
8
9
|
// Base USDC (Circle native)
|
|
9
10
|
const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
10
11
|
const BASE_CHAIN_ID = 8453;
|
|
@@ -50,11 +51,11 @@ export function baseChargeClient(config) {
|
|
|
50
51
|
return Method.toClient(baseChargeMethod, {
|
|
51
52
|
async createCredential({ challenge }) {
|
|
52
53
|
const { request } = challenge;
|
|
53
|
-
const amount =
|
|
54
|
+
const amount = toAtomicAmount(request.amount, request.decimals ?? 6);
|
|
54
55
|
const recipient = request.recipient;
|
|
55
56
|
const currency = (request.currency ?? BASE_USDC);
|
|
56
57
|
// Dynamic imports to keep the module lightweight
|
|
57
|
-
const { createWalletClient, createPublicClient, http
|
|
58
|
+
const { createWalletClient, createPublicClient, http } = await import("viem");
|
|
58
59
|
const { base } = await import("viem/chains");
|
|
59
60
|
const rpcUrl = config.rpcUrl ?? "https://mainnet.base.org";
|
|
60
61
|
const walletClient = createWalletClient({
|
package/dist/core/config.d.ts
CHANGED
|
@@ -13,6 +13,17 @@ export interface CardConfig {
|
|
|
13
13
|
last4: string;
|
|
14
14
|
brand: string;
|
|
15
15
|
}
|
|
16
|
+
export interface SpendPolicy {
|
|
17
|
+
maxPerTxUsd?: number;
|
|
18
|
+
maxPerDayUsd?: number;
|
|
19
|
+
requireConfirmationAboveUsd?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface SpendLedgerEntry {
|
|
22
|
+
method: string;
|
|
23
|
+
amountUsd: number;
|
|
24
|
+
day: string;
|
|
25
|
+
timestamp: string;
|
|
26
|
+
}
|
|
16
27
|
export interface Config {
|
|
17
28
|
apiUrl: string;
|
|
18
29
|
apiKey: string | null;
|
|
@@ -27,6 +38,10 @@ export interface Config {
|
|
|
27
38
|
confirmBeforeSpend: boolean;
|
|
28
39
|
/** Auto-tip amount in USD for successful runs. Default: 0 (no auto-tip). */
|
|
29
40
|
defaultTipAmount: number;
|
|
41
|
+
/** Optional per-method spend policies enforced client-side by MCP tools. */
|
|
42
|
+
spendPolicies?: Record<string, SpendPolicy>;
|
|
43
|
+
/** Daily spend ledger used to enforce max-per-day limits. */
|
|
44
|
+
spendLedger?: SpendLedgerEntry[];
|
|
30
45
|
}
|
|
31
46
|
/** All supported chain identifiers. */
|
|
32
47
|
export declare const SUPPORTED_CHAINS: readonly ["tempo", "base", "solana"];
|
|
@@ -43,6 +58,10 @@ export declare function getApiKey(): string | null;
|
|
|
43
58
|
export declare function isAuthenticated(): boolean;
|
|
44
59
|
export declare function requiresSpendConfirmation(): boolean;
|
|
45
60
|
export declare function getDefaultTipAmount(): number;
|
|
61
|
+
export declare function getSpendPolicy(method: string): SpendPolicy | null;
|
|
62
|
+
export declare function setSpendPolicy(method: string, policy: SpendPolicy): void;
|
|
63
|
+
export declare function getSpendLedger(): SpendLedgerEntry[];
|
|
64
|
+
export declare function saveSpendLedger(entries: SpendLedgerEntry[]): void;
|
|
46
65
|
/**
|
|
47
66
|
* Get all wallets from config + env var synthetic wallets.
|
|
48
67
|
*/
|
package/dist/core/config.js
CHANGED
|
@@ -40,6 +40,8 @@ function migrateIfNeeded(raw) {
|
|
|
40
40
|
favorites: r.favorites ?? [],
|
|
41
41
|
confirmBeforeSpend: r.confirmBeforeSpend !== false,
|
|
42
42
|
defaultTipAmount: typeof r.defaultTipAmount === "number" ? r.defaultTipAmount : 0,
|
|
43
|
+
spendPolicies: r.spendPolicies,
|
|
44
|
+
spendLedger: r.spendLedger,
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
// Build wallets from legacy flat fields
|
|
@@ -111,6 +113,8 @@ function migrateIfNeeded(raw) {
|
|
|
111
113
|
favorites: [],
|
|
112
114
|
confirmBeforeSpend: true,
|
|
113
115
|
defaultTipAmount: 0,
|
|
116
|
+
spendPolicies: {},
|
|
117
|
+
spendLedger: [],
|
|
114
118
|
};
|
|
115
119
|
// Write migrated config (only if there was something to migrate)
|
|
116
120
|
if (raw.tempoPrivateKey || raw.evmPrivateKey || raw.stripeConsumerToken) {
|
|
@@ -132,6 +136,8 @@ export function getConfig() {
|
|
|
132
136
|
favorites: [],
|
|
133
137
|
confirmBeforeSpend: true,
|
|
134
138
|
defaultTipAmount: 0,
|
|
139
|
+
spendPolicies: {},
|
|
140
|
+
spendLedger: [],
|
|
135
141
|
};
|
|
136
142
|
if (!existsSync(CONFIG_FILE)) {
|
|
137
143
|
return defaults;
|
|
@@ -172,6 +178,22 @@ export function requiresSpendConfirmation() {
|
|
|
172
178
|
export function getDefaultTipAmount() {
|
|
173
179
|
return getConfig().defaultTipAmount;
|
|
174
180
|
}
|
|
181
|
+
export function getSpendPolicy(method) {
|
|
182
|
+
const policies = getConfig().spendPolicies ?? {};
|
|
183
|
+
return policies[method] ?? null;
|
|
184
|
+
}
|
|
185
|
+
export function setSpendPolicy(method, policy) {
|
|
186
|
+
const config = getConfig();
|
|
187
|
+
const policies = config.spendPolicies ?? {};
|
|
188
|
+
policies[method] = policy;
|
|
189
|
+
saveConfig({ spendPolicies: policies });
|
|
190
|
+
}
|
|
191
|
+
export function getSpendLedger() {
|
|
192
|
+
return getConfig().spendLedger ?? [];
|
|
193
|
+
}
|
|
194
|
+
export function saveSpendLedger(entries) {
|
|
195
|
+
saveConfig({ spendLedger: entries });
|
|
196
|
+
}
|
|
175
197
|
// ── Wallet helpers ─────────────────────────────────────────────────
|
|
176
198
|
/**
|
|
177
199
|
* Get all wallets from config + env var synthetic wallets.
|
|
@@ -11,7 +11,7 @@ export declare function isFileOutput(output: unknown): output is {
|
|
|
11
11
|
mime_type?: string;
|
|
12
12
|
size_bytes?: number;
|
|
13
13
|
};
|
|
14
|
-
export declare function formatPrice(
|
|
14
|
+
export declare function formatPrice(pricePerRunUsd?: string | null): string;
|
|
15
15
|
interface AgentLike {
|
|
16
16
|
id?: string;
|
|
17
17
|
name?: string;
|
|
@@ -19,8 +19,7 @@ interface AgentLike {
|
|
|
19
19
|
avgRating?: number | null;
|
|
20
20
|
ratingCount?: number;
|
|
21
21
|
totalExecutions?: number;
|
|
22
|
-
|
|
23
|
-
pricingModel?: string;
|
|
22
|
+
pricePerRunUsd?: string;
|
|
24
23
|
stats?: {
|
|
25
24
|
completedJobs?: number;
|
|
26
25
|
avgRating?: number | null;
|
package/dist/core/formatters.js
CHANGED
|
@@ -32,20 +32,18 @@ export function isFileOutput(output) {
|
|
|
32
32
|
return output != null && typeof output === "object" && output.type === "file" && !!output.url;
|
|
33
33
|
}
|
|
34
34
|
// ── Price formatting ─────────────────────────────────────────────
|
|
35
|
-
export function formatPrice(
|
|
36
|
-
if (!
|
|
35
|
+
export function formatPrice(pricePerRunUsd) {
|
|
36
|
+
if (!pricePerRunUsd)
|
|
37
37
|
return "free";
|
|
38
|
-
const p = parseFloat(
|
|
39
|
-
|
|
40
|
-
return `$${p.toFixed(2)}/req`;
|
|
41
|
-
return `$${p.toFixed(3)}/1k tokens`;
|
|
38
|
+
const p = parseFloat(pricePerRunUsd);
|
|
39
|
+
return `$${p.toFixed(2)}/req`;
|
|
42
40
|
}
|
|
43
41
|
export function agentLine(agent) {
|
|
44
42
|
const name = agent.name ?? "Unknown";
|
|
45
43
|
const slug = agent.slug ? ` (${agent.slug})` : "";
|
|
46
44
|
const rating = agent.avgRating ?? agent.stats?.avgRating ?? null;
|
|
47
45
|
const jobs = agent.stats?.completedJobs ?? agent.totalExecutions ?? 0;
|
|
48
|
-
const price = formatPrice(agent.
|
|
46
|
+
const price = formatPrice(agent.pricePerRunUsd);
|
|
49
47
|
const reliability = agent.successRate != null && Number(agent.successRate) < 1
|
|
50
48
|
? ` • ${(Number(agent.successRate) * 100).toFixed(0)}% reliable`
|
|
51
49
|
: "";
|
package/dist/core/payments.js
CHANGED
|
@@ -45,7 +45,7 @@ function clearStaleCardCache(activeKey) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
// ── Per-protocol initializers ───────────────────────────────────
|
|
48
|
-
async function
|
|
48
|
+
async function initEvmMppForChain(wallet, chain) {
|
|
49
49
|
try {
|
|
50
50
|
const { Mppx, tempo } = await import("mppx/client");
|
|
51
51
|
let account;
|
|
@@ -60,8 +60,15 @@ async function initMpp(wallet) {
|
|
|
60
60
|
else {
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
|
-
const
|
|
64
|
-
|
|
63
|
+
const methods = [];
|
|
64
|
+
if (chain === "tempo") {
|
|
65
|
+
methods.push(tempo({ account }));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const { baseChargeClient } = await import("./base-charge.js");
|
|
69
|
+
methods.push(baseChargeClient({ account }));
|
|
70
|
+
}
|
|
71
|
+
const mppx = Mppx.create({ methods: methods });
|
|
65
72
|
return mppx.fetch.bind(mppx);
|
|
66
73
|
}
|
|
67
74
|
catch {
|
|
@@ -127,7 +134,10 @@ async function initForChain(wallet, chain) {
|
|
|
127
134
|
if (chain === "solana") {
|
|
128
135
|
return initSolanaMpp(wallet);
|
|
129
136
|
}
|
|
130
|
-
|
|
137
|
+
if (chain === "tempo" || chain === "base") {
|
|
138
|
+
return initEvmMppForChain(wallet, chain);
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
131
141
|
}
|
|
132
142
|
// ── Public API ──────────────────────────────────────────────────
|
|
133
143
|
/**
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { Credential, Method, z } from "mppx";
|
|
8
8
|
import { Connection, Keypair, PublicKey, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
|
|
9
9
|
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, createTransferCheckedInstruction, getAssociatedTokenAddressSync, } from "@solana/spl-token";
|
|
10
|
+
import { toAtomicAmount } from "./amount-utils.js";
|
|
10
11
|
export const SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
11
12
|
export const SOLANA_CHAIN_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
12
13
|
const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
@@ -67,7 +68,7 @@ export function solanaChargeClient(config) {
|
|
|
67
68
|
return Method.toClient(solanaChargeMethod, {
|
|
68
69
|
async createCredential({ challenge }) {
|
|
69
70
|
const { request } = challenge;
|
|
70
|
-
const amount =
|
|
71
|
+
const amount = toAtomicAmount(request.amount, request.decimals ?? 6);
|
|
71
72
|
const decimals = request.decimals ?? 6;
|
|
72
73
|
const mint = new PublicKey(request.currency ?? SOLANA_USDC_MINT);
|
|
73
74
|
const recipient = new PublicKey(request.recipient);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function getDailySpend(method: string, now?: Date): number;
|
|
2
|
+
export declare function canSpend(params: {
|
|
3
|
+
method: string;
|
|
4
|
+
amountUsd: number;
|
|
5
|
+
}): {
|
|
6
|
+
ok: true;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function requiresPolicyConfirmation(method: string, amountUsd: number): boolean;
|
|
12
|
+
export declare function recordSpend(method: string, amountUsd: number, now?: Date): void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getSpendLedger, getSpendPolicy, saveSpendLedger, } from "./config.js";
|
|
2
|
+
const LEDGER_RETENTION_DAYS = 35;
|
|
3
|
+
function utcDay(now) {
|
|
4
|
+
return now.toISOString().slice(0, 10);
|
|
5
|
+
}
|
|
6
|
+
function pruneLedger(entries, now) {
|
|
7
|
+
const cutoff = new Date(now);
|
|
8
|
+
cutoff.setUTCDate(cutoff.getUTCDate() - LEDGER_RETENTION_DAYS);
|
|
9
|
+
return entries.filter((entry) => new Date(entry.timestamp) >= cutoff);
|
|
10
|
+
}
|
|
11
|
+
export function getDailySpend(method, now = new Date()) {
|
|
12
|
+
const day = utcDay(now);
|
|
13
|
+
return getSpendLedger()
|
|
14
|
+
.filter((entry) => entry.method === method && entry.day === day)
|
|
15
|
+
.reduce((sum, entry) => sum + entry.amountUsd, 0);
|
|
16
|
+
}
|
|
17
|
+
export function canSpend(params) {
|
|
18
|
+
const policy = getSpendPolicy(params.method);
|
|
19
|
+
if (!policy)
|
|
20
|
+
return { ok: true };
|
|
21
|
+
if (policy.maxPerTxUsd != null && params.amountUsd > policy.maxPerTxUsd) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
message: `Transaction blocked by spend policy: $${params.amountUsd.toFixed(2)} exceeds max_per_tx of $${policy.maxPerTxUsd.toFixed(2)} for "${params.method}".`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (policy.maxPerDayUsd != null) {
|
|
28
|
+
const spentToday = getDailySpend(params.method);
|
|
29
|
+
if (spentToday + params.amountUsd > policy.maxPerDayUsd) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
message: `Transaction blocked by spend policy: daily spend would be $${(spentToday + params.amountUsd).toFixed(2)} > $${policy.maxPerDayUsd.toFixed(2)} for "${params.method}".`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { ok: true };
|
|
37
|
+
}
|
|
38
|
+
export function requiresPolicyConfirmation(method, amountUsd) {
|
|
39
|
+
const policy = getSpendPolicy(method);
|
|
40
|
+
if (!policy?.requireConfirmationAboveUsd)
|
|
41
|
+
return false;
|
|
42
|
+
return amountUsd >= policy.requireConfirmationAboveUsd;
|
|
43
|
+
}
|
|
44
|
+
export function recordSpend(method, amountUsd, now = new Date()) {
|
|
45
|
+
const entries = pruneLedger(getSpendLedger(), now);
|
|
46
|
+
entries.push({
|
|
47
|
+
method,
|
|
48
|
+
amountUsd,
|
|
49
|
+
day: utcDay(now),
|
|
50
|
+
timestamp: now.toISOString(),
|
|
51
|
+
});
|
|
52
|
+
saveSpendLedger(entries);
|
|
53
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -5,8 +5,7 @@ export interface AgentRecord {
|
|
|
5
5
|
id: string;
|
|
6
6
|
name: string;
|
|
7
7
|
description?: string;
|
|
8
|
-
|
|
9
|
-
pricingModel?: string;
|
|
8
|
+
pricePerRunUsd?: string;
|
|
10
9
|
avgRating?: number | null;
|
|
11
10
|
totalExecutions?: number;
|
|
12
11
|
successRate?: number | string | null;
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { registerWalletTools } from "./tools/wallet.js";
|
|
|
12
12
|
import { registerFavoriteTools } from "./tools/favorites.js";
|
|
13
13
|
import { registerTipTools } from "./tools/tip.js";
|
|
14
14
|
import { registerPassTools } from "./tools/passes.js";
|
|
15
|
+
import { registerObservabilityTools } from "./tools/observability.js";
|
|
15
16
|
// ── Resources ────────────────────────────────────────────────────
|
|
16
17
|
import { registerAgentResources } from "./resources/agents.js";
|
|
17
18
|
import { registerWalletResources } from "./resources/wallet.js";
|
|
@@ -43,8 +44,9 @@ export async function startMcpServer() {
|
|
|
43
44
|
"- Crypto wallets (Tempo USDC, Base USDC, Solana USDC) are available for advanced users.",
|
|
44
45
|
"- Card and crypto are SEPARATE payment methods. Card charges a credit card; crypto sends USDC on-chain.",
|
|
45
46
|
"- Card is set as the default payment method when configured.",
|
|
46
|
-
"-
|
|
47
|
-
"-
|
|
47
|
+
"- run_agent() and solve() require pay_with explicitly.",
|
|
48
|
+
"- Use wallet_status to see the exact payment methods the user has configured before calling a paid tool.",
|
|
49
|
+
"- Use open_observability_dashboard() to open a secure web usage dashboard for runs/spend/rebates.",
|
|
48
50
|
"- Payment is automatic once configured. Users are never charged for failed runs.",
|
|
49
51
|
"- Do NOT ask the user to set up payment before they try to run an agent. Let them explore freely.",
|
|
50
52
|
"- If a specific payment method fails, report the error clearly. Do NOT silently fall back to a different method.",
|
|
@@ -70,6 +72,7 @@ export async function startMcpServer() {
|
|
|
70
72
|
registerFavoriteTools(server);
|
|
71
73
|
registerTipTools(server);
|
|
72
74
|
registerPassTools(server);
|
|
75
|
+
registerObservabilityTools(server);
|
|
73
76
|
// Register resources
|
|
74
77
|
registerAgentResources(server);
|
|
75
78
|
registerWalletResources(server);
|
package/dist/prompts/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export function registerPrompts(server) {
|
|
|
12
12
|
"1. Check my wallet status with wallet_status",
|
|
13
13
|
"2. If no wallet is configured, help me create one with wallet_setup",
|
|
14
14
|
"3. Show me some popular agents with search_agents",
|
|
15
|
-
"4. Briefly explain how solve, run_agent, buy_agent_credit_pack, rating, and tipping work",
|
|
15
|
+
"4. Briefly explain how solve, run_agent, buy_agent_credit_pack, pay_with selection, rating, and tipping work",
|
|
16
16
|
].join("\n"),
|
|
17
17
|
},
|
|
18
18
|
}],
|
|
@@ -62,7 +62,7 @@ export function registerPrompts(server) {
|
|
|
62
62
|
text: [
|
|
63
63
|
`Complete this task on Agent Wonderland within $${budget || "1.00"}: "${task}"`,
|
|
64
64
|
"",
|
|
65
|
-
"Use the solve tool with the budget parameter. Show me what agent was selected and why.",
|
|
65
|
+
"Use the solve tool with the budget parameter and an explicit pay_with method. Show me what agent was selected and why.",
|
|
66
66
|
].join("\n"),
|
|
67
67
|
},
|
|
68
68
|
}],
|
|
@@ -78,6 +78,8 @@ export function registerPrompts(server) {
|
|
|
78
78
|
text: [
|
|
79
79
|
`Run agent ${agent_id} with this input: ${input}`,
|
|
80
80
|
"",
|
|
81
|
+
"Include an explicit pay_with method when you call run_agent.",
|
|
82
|
+
"",
|
|
81
83
|
"After getting the result:",
|
|
82
84
|
"1. Summarize the output",
|
|
83
85
|
"2. Assess the quality",
|
package/dist/resources/agents.js
CHANGED
|
@@ -7,7 +7,7 @@ export function registerAgentResources(server) {
|
|
|
7
7
|
const lines = agents.map(a => {
|
|
8
8
|
const rating = stars(a.reputationScore);
|
|
9
9
|
const jobs = compactNumber(a.totalExecutions);
|
|
10
|
-
const price = formatPrice(a.
|
|
10
|
+
const price = formatPrice(a.pricePerRunUsd);
|
|
11
11
|
return `${a.name} ${rating} ${jobs} jobs | ${price}/req — ${a.description || ""}`;
|
|
12
12
|
});
|
|
13
13
|
return {
|
package/dist/tools/agent-info.js
CHANGED
|
@@ -20,7 +20,7 @@ export function registerAgentInfoTools(server) {
|
|
|
20
20
|
"",
|
|
21
21
|
a.description ?? "",
|
|
22
22
|
"",
|
|
23
|
-
`Pricing: ${formatPrice(a.
|
|
23
|
+
`Pricing: ${formatPrice(a.pricePerRunUsd)}`,
|
|
24
24
|
`Reliability: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
|
|
25
25
|
`Avg latency: ${a.avgResponseTimeMs != null ? a.avgResponseTimeMs + "ms" : "N/A"}`,
|
|
26
26
|
...(() => {
|
|
@@ -100,7 +100,7 @@ export function registerAgentInfoTools(server) {
|
|
|
100
100
|
return [
|
|
101
101
|
` ${a.name}`,
|
|
102
102
|
` ${stars(rating)} (${s.ratingCount ?? 0} reviews)${tipCount > 0 ? ` • ${tipCount} tips` : ""}`,
|
|
103
|
-
` ${compactNumber(jobs)} jobs • ${formatPrice(a.
|
|
103
|
+
` ${compactNumber(jobs)} jobs • ${formatPrice(a.pricePerRunUsd)}`,
|
|
104
104
|
` Success: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
|
|
105
105
|
` ${agentWebUrl(a.id)}`,
|
|
106
106
|
"",
|
package/dist/tools/favorites.js
CHANGED
|
@@ -34,7 +34,7 @@ export function registerFavoriteTools(server) {
|
|
|
34
34
|
const agent = await apiGet(`/agents/${id}`);
|
|
35
35
|
const rating = stars(agent.avgRating);
|
|
36
36
|
const jobs = compactNumber(agent.totalExecutions);
|
|
37
|
-
const price = formatPrice(agent.
|
|
37
|
+
const price = formatPrice(agent.pricePerRunUsd);
|
|
38
38
|
lines.push(`${agent.name} ${rating} ${jobs} jobs | ${price}`);
|
|
39
39
|
lines.push(` ID: ${id}`);
|
|
40
40
|
if (agent.description)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { apiPost } from "../core/api-client.js";
|
|
2
|
+
function text(t) {
|
|
3
|
+
return { content: [{ type: "text", text: t }] };
|
|
4
|
+
}
|
|
5
|
+
export function registerObservabilityTools(server) {
|
|
6
|
+
server.tool("open_observability_dashboard", "Generate a secure one-click sign-in URL for the Agent Wonderland web observability dashboard. The dashboard shows your agent runs, spend, rebates, and recent activity.", {}, async () => {
|
|
7
|
+
const result = await apiPost("/observability/link", {}, { ensureConsumerPrincipal: true });
|
|
8
|
+
const lines = [
|
|
9
|
+
"Your secure observability link is ready:",
|
|
10
|
+
result.url,
|
|
11
|
+
"",
|
|
12
|
+
`Expires: ${result.expires_at}`,
|
|
13
|
+
];
|
|
14
|
+
if (result.consumer_principal) {
|
|
15
|
+
lines.push(`Consumer principal: ${result.consumer_principal}`);
|
|
16
|
+
}
|
|
17
|
+
lines.push("", "Open the link in your browser to view usage metrics, spend, rebates, and recent runs.");
|
|
18
|
+
return text(lines.join("\n"));
|
|
19
|
+
});
|
|
20
|
+
}
|