@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
package/src/core/formatters.ts
CHANGED
|
@@ -36,11 +36,10 @@ export function isFileOutput(output: unknown): output is { type: "file"; url: st
|
|
|
36
36
|
|
|
37
37
|
// ── Price formatting ─────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
export function formatPrice(
|
|
40
|
-
if (!
|
|
41
|
-
const p = parseFloat(
|
|
42
|
-
|
|
43
|
-
return `$${p.toFixed(3)}/1k tokens`;
|
|
39
|
+
export function formatPrice(pricePerRunUsd?: string | null): string {
|
|
40
|
+
if (!pricePerRunUsd) return "free";
|
|
41
|
+
const p = parseFloat(pricePerRunUsd);
|
|
42
|
+
return `$${p.toFixed(2)}/req`;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
// ── Agent line (compact, one line per agent) ─────────────────────
|
|
@@ -52,8 +51,7 @@ interface AgentLike {
|
|
|
52
51
|
avgRating?: number | null;
|
|
53
52
|
ratingCount?: number;
|
|
54
53
|
totalExecutions?: number;
|
|
55
|
-
|
|
56
|
-
pricingModel?: string;
|
|
54
|
+
pricePerRunUsd?: string;
|
|
57
55
|
stats?: { completedJobs?: number; avgRating?: number | null };
|
|
58
56
|
[key: string]: unknown;
|
|
59
57
|
}
|
|
@@ -63,7 +61,7 @@ export function agentLine(agent: AgentLike): string {
|
|
|
63
61
|
const slug = agent.slug ? ` (${agent.slug})` : "";
|
|
64
62
|
const rating = agent.avgRating ?? agent.stats?.avgRating ?? null;
|
|
65
63
|
const jobs = agent.stats?.completedJobs ?? agent.totalExecutions ?? 0;
|
|
66
|
-
const price = formatPrice(agent.
|
|
64
|
+
const price = formatPrice(agent.pricePerRunUsd);
|
|
67
65
|
const reliability = agent.successRate != null && Number(agent.successRate) < 1
|
|
68
66
|
? ` • ${(Number(agent.successRate) * 100).toFixed(0)}% reliable`
|
|
69
67
|
: "";
|
package/src/core/payments.ts
CHANGED
|
@@ -63,7 +63,10 @@ function clearStaleCardCache(activeKey?: string): void {
|
|
|
63
63
|
|
|
64
64
|
// ── Per-protocol initializers ───────────────────────────────────
|
|
65
65
|
|
|
66
|
-
async function
|
|
66
|
+
async function initEvmMppForChain(
|
|
67
|
+
wallet: WalletEntry,
|
|
68
|
+
chain: "tempo" | "base",
|
|
69
|
+
): Promise<typeof fetch | null> {
|
|
67
70
|
try {
|
|
68
71
|
const { Mppx, tempo } = await import("mppx/client");
|
|
69
72
|
let account;
|
|
@@ -78,8 +81,15 @@ async function initMpp(wallet: WalletEntry): Promise<typeof fetch | null> {
|
|
|
78
81
|
return null;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
const
|
|
82
|
-
|
|
84
|
+
const methods = [];
|
|
85
|
+
if (chain === "tempo") {
|
|
86
|
+
methods.push(tempo({ account }));
|
|
87
|
+
} else {
|
|
88
|
+
const { baseChargeClient } = await import("./base-charge.js");
|
|
89
|
+
methods.push(baseChargeClient({ account }));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const mppx = Mppx.create({ methods: methods as any });
|
|
83
93
|
return mppx.fetch.bind(mppx) as typeof fetch;
|
|
84
94
|
} catch {
|
|
85
95
|
return null;
|
|
@@ -150,7 +160,10 @@ async function initForChain(wallet: WalletEntry, chain: string): Promise<typeof
|
|
|
150
160
|
if (chain === "solana") {
|
|
151
161
|
return initSolanaMpp(wallet);
|
|
152
162
|
}
|
|
153
|
-
|
|
163
|
+
if (chain === "tempo" || chain === "base") {
|
|
164
|
+
return initEvmMppForChain(wallet, chain);
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
154
167
|
}
|
|
155
168
|
|
|
156
169
|
// ── Public API ──────────────────────────────────────────────────
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getAssociatedTokenAddressSync,
|
|
14
14
|
} from "@solana/spl-token";
|
|
15
15
|
import type { WalletEntry } from "./config.js";
|
|
16
|
+
import { toAtomicAmount } from "./amount-utils.js";
|
|
16
17
|
|
|
17
18
|
export const SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" as const;
|
|
18
19
|
export const SOLANA_CHAIN_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" as const;
|
|
@@ -98,7 +99,7 @@ export function solanaChargeClient(config: SolanaChargeClientConfig) {
|
|
|
98
99
|
return Method.toClient(solanaChargeMethod as any, {
|
|
99
100
|
async createCredential({ challenge }: any) {
|
|
100
101
|
const { request } = challenge;
|
|
101
|
-
const amount =
|
|
102
|
+
const amount = toAtomicAmount(request.amount, request.decimals ?? 6);
|
|
102
103
|
const decimals = request.decimals ?? 6;
|
|
103
104
|
const mint = new PublicKey(request.currency ?? SOLANA_USDC_MINT);
|
|
104
105
|
const recipient = new PublicKey(request.recipient);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSpendLedger,
|
|
3
|
+
getSpendPolicy,
|
|
4
|
+
saveSpendLedger,
|
|
5
|
+
type SpendLedgerEntry,
|
|
6
|
+
} from "./config.js";
|
|
7
|
+
|
|
8
|
+
const LEDGER_RETENTION_DAYS = 35;
|
|
9
|
+
|
|
10
|
+
function utcDay(now: Date): string {
|
|
11
|
+
return now.toISOString().slice(0, 10);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function pruneLedger(entries: SpendLedgerEntry[], now: Date): SpendLedgerEntry[] {
|
|
15
|
+
const cutoff = new Date(now);
|
|
16
|
+
cutoff.setUTCDate(cutoff.getUTCDate() - LEDGER_RETENTION_DAYS);
|
|
17
|
+
return entries.filter((entry) => new Date(entry.timestamp) >= cutoff);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getDailySpend(method: string, now = new Date()): number {
|
|
21
|
+
const day = utcDay(now);
|
|
22
|
+
return getSpendLedger()
|
|
23
|
+
.filter((entry) => entry.method === method && entry.day === day)
|
|
24
|
+
.reduce((sum, entry) => sum + entry.amountUsd, 0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function canSpend(params: {
|
|
28
|
+
method: string;
|
|
29
|
+
amountUsd: number;
|
|
30
|
+
}): { ok: true } | { ok: false; message: string } {
|
|
31
|
+
const policy = getSpendPolicy(params.method);
|
|
32
|
+
if (!policy) return { ok: true };
|
|
33
|
+
|
|
34
|
+
if (policy.maxPerTxUsd != null && params.amountUsd > policy.maxPerTxUsd) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
message: `Transaction blocked by spend policy: $${params.amountUsd.toFixed(2)} exceeds max_per_tx of $${policy.maxPerTxUsd.toFixed(2)} for "${params.method}".`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (policy.maxPerDayUsd != null) {
|
|
42
|
+
const spentToday = getDailySpend(params.method);
|
|
43
|
+
if (spentToday + params.amountUsd > policy.maxPerDayUsd) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
message: `Transaction blocked by spend policy: daily spend would be $${(spentToday + params.amountUsd).toFixed(2)} > $${policy.maxPerDayUsd.toFixed(2)} for "${params.method}".`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { ok: true };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function requiresPolicyConfirmation(method: string, amountUsd: number): boolean {
|
|
55
|
+
const policy = getSpendPolicy(method);
|
|
56
|
+
if (!policy?.requireConfirmationAboveUsd) return false;
|
|
57
|
+
return amountUsd >= policy.requireConfirmationAboveUsd;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function recordSpend(method: string, amountUsd: number, now = new Date()): void {
|
|
61
|
+
const entries = pruneLedger(getSpendLedger(), now);
|
|
62
|
+
entries.push({
|
|
63
|
+
method,
|
|
64
|
+
amountUsd,
|
|
65
|
+
day: utcDay(now),
|
|
66
|
+
timestamp: now.toISOString(),
|
|
67
|
+
});
|
|
68
|
+
saveSpendLedger(entries);
|
|
69
|
+
}
|
package/src/core/types.ts
CHANGED
|
@@ -6,8 +6,7 @@ export interface AgentRecord {
|
|
|
6
6
|
id: string;
|
|
7
7
|
name: string;
|
|
8
8
|
description?: string;
|
|
9
|
-
|
|
10
|
-
pricingModel?: string;
|
|
9
|
+
pricePerRunUsd?: string;
|
|
11
10
|
avgRating?: number | null;
|
|
12
11
|
totalExecutions?: number;
|
|
13
12
|
successRate?: number | string | null;
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { registerWalletTools } from "./tools/wallet.js";
|
|
|
14
14
|
import { registerFavoriteTools } from "./tools/favorites.js";
|
|
15
15
|
import { registerTipTools } from "./tools/tip.js";
|
|
16
16
|
import { registerPassTools } from "./tools/passes.js";
|
|
17
|
+
import { registerObservabilityTools } from "./tools/observability.js";
|
|
17
18
|
|
|
18
19
|
// ── Resources ────────────────────────────────────────────────────
|
|
19
20
|
import { registerAgentResources } from "./resources/agents.js";
|
|
@@ -50,8 +51,9 @@ export async function startMcpServer(): Promise<void> {
|
|
|
50
51
|
"- Crypto wallets (Tempo USDC, Base USDC, Solana USDC) are available for advanced users.",
|
|
51
52
|
"- Card and crypto are SEPARATE payment methods. Card charges a credit card; crypto sends USDC on-chain.",
|
|
52
53
|
"- Card is set as the default payment method when configured.",
|
|
53
|
-
"-
|
|
54
|
-
"-
|
|
54
|
+
"- run_agent() and solve() require pay_with explicitly.",
|
|
55
|
+
"- Use wallet_status to see the exact payment methods the user has configured before calling a paid tool.",
|
|
56
|
+
"- Use open_observability_dashboard() to open a secure web usage dashboard for runs/spend/rebates.",
|
|
55
57
|
"- Payment is automatic once configured. Users are never charged for failed runs.",
|
|
56
58
|
"- Do NOT ask the user to set up payment before they try to run an agent. Let them explore freely.",
|
|
57
59
|
"- If a specific payment method fails, report the error clearly. Do NOT silently fall back to a different method.",
|
|
@@ -79,6 +81,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
79
81
|
registerFavoriteTools(server);
|
|
80
82
|
registerTipTools(server);
|
|
81
83
|
registerPassTools(server);
|
|
84
|
+
registerObservabilityTools(server);
|
|
82
85
|
|
|
83
86
|
// Register resources
|
|
84
87
|
registerAgentResources(server);
|
package/src/prompts/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ export function registerPrompts(server: McpServer) {
|
|
|
18
18
|
"1. Check my wallet status with wallet_status",
|
|
19
19
|
"2. If no wallet is configured, help me create one with wallet_setup",
|
|
20
20
|
"3. Show me some popular agents with search_agents",
|
|
21
|
-
"4. Briefly explain how solve, run_agent, buy_agent_credit_pack, rating, and tipping work",
|
|
21
|
+
"4. Briefly explain how solve, run_agent, buy_agent_credit_pack, pay_with selection, rating, and tipping work",
|
|
22
22
|
].join("\n"),
|
|
23
23
|
},
|
|
24
24
|
}],
|
|
@@ -86,7 +86,7 @@ export function registerPrompts(server: McpServer) {
|
|
|
86
86
|
text: [
|
|
87
87
|
`Complete this task on Agent Wonderland within $${budget || "1.00"}: "${task}"`,
|
|
88
88
|
"",
|
|
89
|
-
"Use the solve tool with the budget parameter. Show me what agent was selected and why.",
|
|
89
|
+
"Use the solve tool with the budget parameter and an explicit pay_with method. Show me what agent was selected and why.",
|
|
90
90
|
].join("\n"),
|
|
91
91
|
},
|
|
92
92
|
}],
|
|
@@ -108,6 +108,8 @@ export function registerPrompts(server: McpServer) {
|
|
|
108
108
|
text: [
|
|
109
109
|
`Run agent ${agent_id} with this input: ${input}`,
|
|
110
110
|
"",
|
|
111
|
+
"Include an explicit pay_with method when you call run_agent.",
|
|
112
|
+
"",
|
|
111
113
|
"After getting the result:",
|
|
112
114
|
"1. Summarize the output",
|
|
113
115
|
"2. Assess the quality",
|
package/src/resources/agents.ts
CHANGED
|
@@ -9,7 +9,7 @@ export function registerAgentResources(server: McpServer) {
|
|
|
9
9
|
const lines = agents.map(a => {
|
|
10
10
|
const rating = stars(a.reputationScore);
|
|
11
11
|
const jobs = compactNumber(a.totalExecutions);
|
|
12
|
-
const price = formatPrice(a.
|
|
12
|
+
const price = formatPrice(a.pricePerRunUsd);
|
|
13
13
|
return `${a.name} ${rating} ${jobs} jobs | ${price}/req — ${a.description || ""}`;
|
|
14
14
|
});
|
|
15
15
|
return {
|
package/src/tools/agent-info.ts
CHANGED
|
@@ -37,7 +37,7 @@ export function registerAgentInfoTools(server: McpServer): void {
|
|
|
37
37
|
"",
|
|
38
38
|
(a.description as string) ?? "",
|
|
39
39
|
"",
|
|
40
|
-
`Pricing: ${formatPrice(a.
|
|
40
|
+
`Pricing: ${formatPrice(a.pricePerRunUsd)}`,
|
|
41
41
|
`Reliability: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
|
|
42
42
|
`Avg latency: ${(a.avgResponseTimeMs as number) != null ? a.avgResponseTimeMs + "ms" : "N/A"}`,
|
|
43
43
|
...(() => {
|
|
@@ -130,7 +130,7 @@ export function registerAgentInfoTools(server: McpServer): void {
|
|
|
130
130
|
return [
|
|
131
131
|
` ${a.name}`,
|
|
132
132
|
` ${stars(rating)} (${s.ratingCount ?? 0} reviews)${tipCount > 0 ? ` • ${tipCount} tips` : ""}`,
|
|
133
|
-
` ${compactNumber(jobs)} jobs • ${formatPrice(a.
|
|
133
|
+
` ${compactNumber(jobs)} jobs • ${formatPrice(a.pricePerRunUsd)}`,
|
|
134
134
|
` Success: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
|
|
135
135
|
` ${agentWebUrl(a.id)}`,
|
|
136
136
|
"",
|
package/src/tools/favorites.ts
CHANGED
|
@@ -54,10 +54,7 @@ export function registerFavoriteTools(server: McpServer) {
|
|
|
54
54
|
const agent = await apiGet<Record<string, unknown>>(`/agents/${id}`);
|
|
55
55
|
const rating = stars(agent.avgRating as number | null | undefined);
|
|
56
56
|
const jobs = compactNumber(agent.totalExecutions as number | null | undefined);
|
|
57
|
-
const price = formatPrice(
|
|
58
|
-
agent.pricePer1kTokens as string | null | undefined,
|
|
59
|
-
agent.pricingModel as string | null | undefined,
|
|
60
|
-
);
|
|
57
|
+
const price = formatPrice(agent.pricePerRunUsd as string | null | undefined);
|
|
61
58
|
lines.push(`${agent.name} ${rating} ${jobs} jobs | ${price}`);
|
|
62
59
|
lines.push(` ID: ${id}`);
|
|
63
60
|
if (agent.description) lines.push(` ${agent.description}`);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { apiPost } from "../core/api-client.js";
|
|
3
|
+
|
|
4
|
+
function text(t: string) {
|
|
5
|
+
return { content: [{ type: "text" as const, text: t }] };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function registerObservabilityTools(server: McpServer): void {
|
|
9
|
+
server.tool(
|
|
10
|
+
"open_observability_dashboard",
|
|
11
|
+
"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.",
|
|
12
|
+
{},
|
|
13
|
+
async () => {
|
|
14
|
+
const result = await apiPost<{
|
|
15
|
+
url: string;
|
|
16
|
+
expires_at: string;
|
|
17
|
+
consumer_principal?: string;
|
|
18
|
+
}>(
|
|
19
|
+
"/observability/link",
|
|
20
|
+
{},
|
|
21
|
+
{ ensureConsumerPrincipal: true },
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const lines = [
|
|
25
|
+
"Your secure observability link is ready:",
|
|
26
|
+
result.url,
|
|
27
|
+
"",
|
|
28
|
+
`Expires: ${result.expires_at}`,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
if (result.consumer_principal) {
|
|
32
|
+
lines.push(`Consumer principal: ${result.consumer_principal}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
lines.push(
|
|
36
|
+
"",
|
|
37
|
+
"Open the link in your browser to view usage metrics, spend, rebates, and recent runs.",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return text(lines.join("\n"));
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
}
|
package/src/tools/passes.ts
CHANGED
|
@@ -155,7 +155,6 @@ export function registerPassTools(server: McpServer): void {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
|
|
158
|
-
|
|
159
158
|
if (requiresSpendConfirmation() && !confirmed) {
|
|
160
159
|
pendingCreditPackPurchases.set(agent.id, {
|
|
161
160
|
agentId: agent.id,
|
|
@@ -182,12 +181,7 @@ export function registerPassTools(server: McpServer): void {
|
|
|
182
181
|
consumer_principal: string;
|
|
183
182
|
offer: CreditPackOffer;
|
|
184
183
|
credit_pack: CreditPackRecord;
|
|
185
|
-
}>(
|
|
186
|
-
`/agents/${agent.id}/credit-packs/purchase`,
|
|
187
|
-
{ pack_id: resolvedOffer.pack_id },
|
|
188
|
-
method,
|
|
189
|
-
{ ensureConsumerPrincipal: true },
|
|
190
|
-
);
|
|
184
|
+
}>(`/agents/${agent.id}/credit-packs/purchase`, { pack_id: resolvedOffer.pack_id }, method, { ensureConsumerPrincipal: true });
|
|
191
185
|
|
|
192
186
|
pendingCreditPackPurchases.delete(agent.id);
|
|
193
187
|
|
package/src/tools/run.ts
CHANGED
|
@@ -17,10 +17,10 @@ import {
|
|
|
17
17
|
} from "../core/payments.js";
|
|
18
18
|
import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
|
|
19
19
|
import { formatRunResult } from "../core/formatters.js";
|
|
20
|
+
import { canSpend, recordSpend, requiresPolicyConfirmation } from "../core/spend-policy.js";
|
|
20
21
|
import { storeFeedbackToken } from "./_token-cache.js";
|
|
21
22
|
import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
|
|
22
23
|
import {
|
|
23
|
-
formatPaymentChoicePrompt,
|
|
24
24
|
formatPaymentLabel,
|
|
25
25
|
formatRunConfirmationCommand,
|
|
26
26
|
resolveConfirmationMethod,
|
|
@@ -100,7 +100,7 @@ export function registerRunTools(server: McpServer): void {
|
|
|
100
100
|
{
|
|
101
101
|
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
102
102
|
input: z.record(z.unknown()).describe("Input payload for the agent"),
|
|
103
|
-
pay_with: z.string().
|
|
103
|
+
pay_with: z.string().trim().min(1).describe("Required payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Use wallet_status to see configured options."),
|
|
104
104
|
confirmed: z.boolean().optional().describe("Set to true to confirm spending after seeing the price quote."),
|
|
105
105
|
},
|
|
106
106
|
async ({ agent_id, input, pay_with, confirmed }) => {
|
|
@@ -124,24 +124,23 @@ export function registerRunTools(server: McpServer): void {
|
|
|
124
124
|
return text(`Agent "${agent_id}" not found. Use search_agents to find available agents.`);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
const price = parseFloat(agent.
|
|
127
|
+
const price = parseFloat(agent.pricePerRunUsd ?? "0.01");
|
|
128
128
|
const agentName = agent.name ?? agent_id;
|
|
129
129
|
const creditPackInventory = await getCreditPackInventory(agent.id);
|
|
130
130
|
const activeCreditPack = getActiveCreditPack(creditPackInventory);
|
|
131
|
-
const
|
|
132
|
-
const compatibleMethods = getCompatiblePaymentMethods(agent, configuredMethods);
|
|
131
|
+
const compatibleMethods = getCompatiblePaymentMethods(agent, getConfiguredMethods());
|
|
133
132
|
const pending = pendingRuns.get(agent.id);
|
|
134
|
-
const requestedMethod = pay_with
|
|
135
|
-
const normalizedRequestedMethod =
|
|
133
|
+
const requestedMethod = pay_with;
|
|
134
|
+
const normalizedRequestedMethod = normalizePaymentMethod(requestedMethod);
|
|
136
135
|
|
|
137
|
-
if (!
|
|
136
|
+
if (!normalizedRequestedMethod) {
|
|
138
137
|
return text(
|
|
139
138
|
`Payment method "${requestedMethod}" is not configured.\n\n` +
|
|
140
139
|
"Use wallet_status to review your current payment methods.",
|
|
141
140
|
);
|
|
142
141
|
}
|
|
143
142
|
|
|
144
|
-
if (!
|
|
143
|
+
if (!compatibleMethods.includes(normalizedRequestedMethod)) {
|
|
145
144
|
return text(
|
|
146
145
|
`This agent cannot be paid with "${requestedMethod}".\n\n` +
|
|
147
146
|
`Available payment methods for this agent: ${compatibleMethods.join(", ") || "none"}.\n` +
|
|
@@ -149,30 +148,21 @@ export function registerRunTools(server: McpServer): void {
|
|
|
149
148
|
);
|
|
150
149
|
}
|
|
151
150
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`No compatible payment methods are configured for ${agentName}.\n\n` +
|
|
155
|
-
`Your configured methods: ${configuredMethods.join(", ") || "none"}\n` +
|
|
156
|
-
`Agent accepts: ${agent.payment?.accepted_payments?.join(", ") || "unknown"}\n` +
|
|
157
|
-
"Use wallet_status to review your current setup.",
|
|
158
|
-
);
|
|
159
|
-
}
|
|
151
|
+
const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
|
|
152
|
+
const spendCheckMethod = method ?? normalizedRequestedMethod;
|
|
160
153
|
|
|
161
|
-
if (!activeCreditPack
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
168
|
-
|
|
154
|
+
if (!activeCreditPack) {
|
|
155
|
+
const spendCheck = canSpend({
|
|
156
|
+
method: spendCheckMethod,
|
|
157
|
+
amountUsd: price,
|
|
158
|
+
});
|
|
159
|
+
if (!spendCheck.ok) {
|
|
160
|
+
return text(spendCheck.message);
|
|
161
|
+
}
|
|
169
162
|
}
|
|
170
163
|
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
: resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
|
|
174
|
-
|
|
175
|
-
if (!activeCreditPack && requiresSpendConfirmation() && !confirmed) {
|
|
164
|
+
const needsPolicyConfirmation = !activeCreditPack && requiresPolicyConfirmation(spendCheckMethod, price);
|
|
165
|
+
if (!activeCreditPack && (requiresSpendConfirmation() || needsPolicyConfirmation) && !confirmed) {
|
|
176
166
|
pendingRuns.set(agent.id, {
|
|
177
167
|
agent: { id: agent.id, name: agentName, price },
|
|
178
168
|
input,
|
|
@@ -218,26 +208,37 @@ export function registerRunTools(server: McpServer): void {
|
|
|
218
208
|
}
|
|
219
209
|
|
|
220
210
|
let result: Record<string, unknown>;
|
|
211
|
+
let usedPaidMethod = !activeCreditPack;
|
|
221
212
|
try {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
213
|
+
if (activeCreditPack) {
|
|
214
|
+
try {
|
|
215
|
+
result = await apiPost<Record<string, unknown>>(
|
|
216
|
+
`/agents/${agent.id}/run`,
|
|
217
|
+
{ input: processedInput },
|
|
218
|
+
{ ensureConsumerPrincipal: true },
|
|
219
|
+
);
|
|
220
|
+
} catch (packErr) {
|
|
221
|
+
const packApiErr = packErr as { status?: number };
|
|
222
|
+
if (packApiErr?.status !== 402) throw packErr;
|
|
223
|
+
result = await apiPostWithPayment<Record<string, unknown>>(
|
|
224
|
+
`/agents/${agent.id}/run`,
|
|
225
|
+
{ input: processedInput },
|
|
226
|
+
method,
|
|
227
|
+
);
|
|
228
|
+
usedPaidMethod = true;
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
result = await apiPostWithPayment<Record<string, unknown>>(
|
|
229
232
|
`/agents/${agent.id}/run`,
|
|
230
233
|
{ input: processedInput },
|
|
231
234
|
method,
|
|
232
235
|
);
|
|
236
|
+
}
|
|
237
|
+
if (usedPaidMethod) {
|
|
238
|
+
recordSpend(spendCheckMethod, price);
|
|
239
|
+
}
|
|
233
240
|
} catch (err: unknown) {
|
|
234
241
|
const apiErr = err as { status?: number; message?: string };
|
|
235
|
-
if (activeCreditPack && apiErr?.status === 402) {
|
|
236
|
-
return text(
|
|
237
|
-
`Your available credit packs for ${agentName} could not cover this run.\n\n` +
|
|
238
|
-
"Use list_agent_credit_packs to inspect your balances, or retry with a payment method.",
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
242
|
if (apiErr?.status === 402) {
|
|
242
243
|
const allMethods = getConfiguredMethods();
|
|
243
244
|
const methodName = method ?? allMethods[0] ?? "auto";
|
package/src/tools/solve.ts
CHANGED
|
@@ -13,11 +13,11 @@ import {
|
|
|
13
13
|
} from "../core/payments.js";
|
|
14
14
|
import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
|
|
15
15
|
import { agentList, formatRunResult } from "../core/formatters.js";
|
|
16
|
+
import { canSpend, recordSpend, requiresPolicyConfirmation } from "../core/spend-policy.js";
|
|
16
17
|
import { uploadLocalFiles } from "../core/file-upload.js";
|
|
17
18
|
import type { AgentRecord } from "../core/types.js";
|
|
18
19
|
import { storeFeedbackToken } from "./_token-cache.js";
|
|
19
20
|
import {
|
|
20
|
-
formatPaymentChoicePrompt,
|
|
21
21
|
formatPaymentLabel,
|
|
22
22
|
formatSolveConfirmationCommand,
|
|
23
23
|
makeSolvePendingKey,
|
|
@@ -137,8 +137,9 @@ export function registerSolveTools(server: McpServer): void {
|
|
|
137
137
|
.describe("Maximum budget in USD"),
|
|
138
138
|
pay_with: z
|
|
139
139
|
.string()
|
|
140
|
-
.
|
|
141
|
-
.
|
|
140
|
+
.trim()
|
|
141
|
+
.min(1)
|
|
142
|
+
.describe("Required payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Use wallet_status to see configured options."),
|
|
142
143
|
confirmed: z
|
|
143
144
|
.boolean()
|
|
144
145
|
.optional()
|
|
@@ -161,10 +162,10 @@ export function registerSolveTools(server: McpServer): void {
|
|
|
161
162
|
const pendingKey = makeSolvePendingKey(intent, input, budget);
|
|
162
163
|
const pending = pendingSolves.get(pendingKey);
|
|
163
164
|
const configuredMethods = getConfiguredMethods();
|
|
164
|
-
const requestedMethod = pay_with
|
|
165
|
-
const normalizedRequestedMethod =
|
|
165
|
+
const requestedMethod = pay_with;
|
|
166
|
+
const normalizedRequestedMethod = normalizePaymentMethod(requestedMethod);
|
|
166
167
|
|
|
167
|
-
if (
|
|
168
|
+
if (!normalizedRequestedMethod) {
|
|
168
169
|
return text(
|
|
169
170
|
`Payment method "${requestedMethod}" is not configured.\n\n` +
|
|
170
171
|
"Use wallet_status to review your current payment methods.",
|
|
@@ -220,17 +221,15 @@ export function registerSolveTools(server: McpServer): void {
|
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
const discovery = agentList(agents, intent);
|
|
223
|
-
const inputTokens = Math.ceil(JSON.stringify(input).length / 4);
|
|
224
224
|
const affordable = agents.filter((agent) => {
|
|
225
|
-
const price = parseFloat(agent.
|
|
226
|
-
|
|
227
|
-
return cost <= budget;
|
|
225
|
+
const price = parseFloat(agent.pricePerRunUsd ?? "0.01");
|
|
226
|
+
return price <= budget;
|
|
228
227
|
});
|
|
229
228
|
const selected = affordable[0] ?? agents[0];
|
|
230
229
|
const activeCreditPack = await getCreditPackInventory(selected.id).then(getActiveCreditPack);
|
|
231
230
|
const compatibleMethods = getCompatiblePaymentMethods(selected, configuredMethods);
|
|
232
231
|
|
|
233
|
-
if (!
|
|
232
|
+
if (!compatibleMethods.includes(normalizedRequestedMethod)) {
|
|
234
233
|
return text(
|
|
235
234
|
`The best matching agent cannot be paid with "${requestedMethod}".\n\n` +
|
|
236
235
|
`Available payment methods for ${selected.name}: ${compatibleMethods.join(", ") || "none"}.\n` +
|
|
@@ -238,39 +237,14 @@ export function registerSolveTools(server: McpServer): void {
|
|
|
238
237
|
);
|
|
239
238
|
}
|
|
240
239
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
`No compatible payment methods are configured for ${selected.name}.\n\n` +
|
|
244
|
-
`Your configured methods: ${configuredMethods.join(", ") || "none"}\n` +
|
|
245
|
-
`Agent accepts: ${selected.payment?.accepted_payments?.join(", ") || "unknown"}\n` +
|
|
246
|
-
"Use wallet_status to review your current setup.",
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (!activeCreditPack && !requestedMethod && compatibleMethods.length > 1) {
|
|
251
|
-
return text([
|
|
252
|
-
discovery,
|
|
253
|
-
"",
|
|
254
|
-
formatPaymentChoicePrompt(
|
|
255
|
-
selected.name ?? selected.id,
|
|
256
|
-
compatibleMethods,
|
|
257
|
-
compatibleMethods.map((method) =>
|
|
258
|
-
formatSolveConfirmationCommand(intent, budget, method).replace(", confirmed: true", ""),
|
|
259
|
-
),
|
|
260
|
-
),
|
|
261
|
-
].join("\n"));
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const method = activeCreditPack
|
|
265
|
-
? undefined
|
|
266
|
-
: resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
|
|
240
|
+
const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
|
|
241
|
+
const spendCheckMethod = method ?? normalizedRequestedMethod;
|
|
267
242
|
|
|
268
|
-
const selectedPrice = parseFloat(selected.
|
|
269
|
-
const estimatedCost =
|
|
270
|
-
? selectedPrice
|
|
271
|
-
: (inputTokens / 1000) * selectedPrice;
|
|
243
|
+
const selectedPrice = parseFloat(selected.pricePerRunUsd ?? "0.01");
|
|
244
|
+
const estimatedCost = selectedPrice;
|
|
272
245
|
|
|
273
|
-
|
|
246
|
+
const needsPolicyConfirmation = !activeCreditPack && requiresPolicyConfirmation(spendCheckMethod, estimatedCost);
|
|
247
|
+
if (!activeCreditPack && (requiresSpendConfirmation() || needsPolicyConfirmation) && !confirmed) {
|
|
274
248
|
pendingSolves.set(pendingKey, { method });
|
|
275
249
|
return text([
|
|
276
250
|
discovery,
|
|
@@ -291,26 +265,47 @@ export function registerSolveTools(server: McpServer): void {
|
|
|
291
265
|
}
|
|
292
266
|
|
|
293
267
|
let result: Record<string, unknown>;
|
|
268
|
+
if (!activeCreditPack) {
|
|
269
|
+
const spendCheck = canSpend({
|
|
270
|
+
method: spendCheckMethod,
|
|
271
|
+
amountUsd: estimatedCost,
|
|
272
|
+
});
|
|
273
|
+
if (!spendCheck.ok) {
|
|
274
|
+
return text(spendCheck.message);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
294
278
|
try {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
279
|
+
let usedPaidMethod = !activeCreditPack;
|
|
280
|
+
if (activeCreditPack) {
|
|
281
|
+
try {
|
|
282
|
+
result = await apiPost<Record<string, unknown>>(
|
|
283
|
+
`/agents/${selected.id}/run`,
|
|
284
|
+
{ input: processedInput },
|
|
285
|
+
{ ensureConsumerPrincipal: true },
|
|
286
|
+
);
|
|
287
|
+
} catch (packErr) {
|
|
288
|
+
const packApiErr = packErr as { status?: number };
|
|
289
|
+
if (packApiErr?.status !== 402) throw packErr;
|
|
290
|
+
result = await apiPostWithPayment<Record<string, unknown>>(
|
|
291
|
+
`/agents/${selected.id}/run`,
|
|
292
|
+
{ input: processedInput },
|
|
293
|
+
method,
|
|
294
|
+
);
|
|
295
|
+
usedPaidMethod = true;
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
result = await apiPostWithPayment<Record<string, unknown>>(
|
|
302
299
|
`/agents/${selected.id}/run`,
|
|
303
300
|
{ input: processedInput },
|
|
304
301
|
method,
|
|
305
302
|
);
|
|
303
|
+
}
|
|
304
|
+
if (usedPaidMethod) {
|
|
305
|
+
recordSpend(spendCheckMethod, estimatedCost);
|
|
306
|
+
}
|
|
306
307
|
} catch (err: unknown) {
|
|
307
308
|
const apiErr = err as { status?: number; message?: string };
|
|
308
|
-
if (activeCreditPack && apiErr?.status === 402) {
|
|
309
|
-
return text(
|
|
310
|
-
`Your available credit packs for ${selected.name} could not cover this run.\n\n` +
|
|
311
|
-
"Use list_agent_credit_packs to inspect your balances, or retry with a payment method.",
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
309
|
if (apiErr?.status === 402) {
|
|
315
310
|
return text(
|
|
316
311
|
[
|