@agentwonderland/mcp 0.1.24 → 0.1.26
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__/api-client.test.d.ts +1 -0
- package/dist/core/__tests__/api-client.test.js +51 -0
- package/dist/core/__tests__/formatters.test.js +10 -0
- package/dist/core/__tests__/passes-api.test.d.ts +1 -0
- package/dist/core/__tests__/passes-api.test.js +27 -0
- package/dist/core/__tests__/payments.test.js +59 -6
- package/dist/core/__tests__/principal.test.js +41 -4
- package/dist/core/__tests__/solana-charge.test.d.ts +1 -0
- package/dist/core/__tests__/solana-charge.test.js +50 -0
- 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/api-client.d.ts +1 -0
- package/dist/core/api-client.js +8 -3
- package/dist/core/balances.d.ts +1 -0
- package/dist/core/balances.js +56 -0
- package/dist/core/base-charge.js +16 -8
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.js +22 -0
- package/dist/core/formatters.d.ts +5 -5
- package/dist/core/formatters.js +12 -8
- package/dist/core/passes.d.ts +1 -1
- package/dist/core/passes.js +5 -2
- package/dist/core/payments.d.ts +1 -0
- package/dist/core/payments.js +32 -9
- package/dist/core/principal.d.ts +3 -0
- package/dist/core/principal.js +29 -1
- package/dist/core/settings.d.ts +20 -0
- package/dist/core/settings.js +19 -0
- package/dist/core/solana-charge.d.ts +5 -0
- package/dist/core/solana-charge.js +31 -8
- package/dist/core/spend-policy.d.ts +12 -0
- package/dist/core/spend-policy.js +53 -0
- package/dist/core/tempo-charge.d.ts +7 -0
- package/dist/core/tempo-charge.js +84 -0
- package/dist/core/types.d.ts +1 -2
- package/dist/index.js +9 -5
- package/dist/prompts/index.js +4 -2
- package/dist/resources/agents.js +1 -1
- package/dist/tools/__tests__/jobs.test.d.ts +1 -0
- package/dist/tools/__tests__/jobs.test.js +71 -0
- package/dist/tools/__tests__/run.test.d.ts +1 -0
- package/dist/tools/__tests__/run.test.js +149 -0
- package/dist/tools/__tests__/solve.test.d.ts +1 -0
- package/dist/tools/__tests__/solve.test.js +158 -0
- package/dist/tools/__tests__/wallet.test.d.ts +1 -0
- package/dist/tools/__tests__/wallet.test.js +230 -0
- package/dist/tools/_payment-confirmation.js +1 -1
- package/dist/tools/agent-info.js +2 -2
- package/dist/tools/favorites.js +1 -1
- package/dist/tools/jobs.js +8 -1
- package/dist/tools/observability.d.ts +2 -0
- package/dist/tools/observability.js +20 -0
- package/dist/tools/passes.js +11 -6
- package/dist/tools/run.js +45 -29
- package/dist/tools/solve.js +53 -40
- package/dist/tools/wallet.js +58 -22
- package/package.json +2 -2
- package/src/core/__tests__/amount-utils.test.ts +13 -0
- package/src/core/__tests__/api-client.test.ts +78 -0
- package/src/core/__tests__/formatters.test.ts +12 -0
- package/src/core/__tests__/passes-api.test.ts +33 -0
- package/src/core/__tests__/payments.test.ts +79 -6
- package/src/core/__tests__/principal.test.ts +49 -4
- package/src/core/__tests__/solana-charge.test.ts +59 -0
- package/src/core/__tests__/spend-policy.test.ts +58 -0
- package/src/core/amount-utils.ts +5 -0
- package/src/core/api-client.ts +16 -3
- package/src/core/balances.ts +63 -0
- package/src/core/base-charge.ts +16 -8
- package/src/core/config.ts +45 -0
- package/src/core/formatters.ts +16 -11
- package/src/core/passes.ts +5 -2
- package/src/core/payments.ts +37 -9
- package/src/core/principal.ts +42 -1
- package/src/core/settings.ts +36 -0
- package/src/core/solana-charge.ts +45 -10
- package/src/core/spend-policy.ts +69 -0
- package/src/core/tempo-charge.ts +104 -0
- package/src/core/types.ts +1 -2
- package/src/index.ts +9 -5
- package/src/prompts/index.ts +4 -2
- package/src/resources/agents.ts +1 -1
- package/src/tools/__tests__/jobs.test.ts +89 -0
- package/src/tools/__tests__/run.test.ts +176 -0
- package/src/tools/__tests__/solve.test.ts +186 -0
- package/src/tools/__tests__/wallet.test.ts +289 -0
- package/src/tools/_payment-confirmation.ts +1 -1
- package/src/tools/agent-info.ts +2 -2
- package/src/tools/favorites.ts +1 -4
- package/src/tools/jobs.ts +10 -1
- package/src/tools/observability.ts +43 -0
- package/src/tools/passes.ts +12 -12
- package/src/tools/run.ts +50 -41
- package/src/tools/solve.ts +58 -52
- package/src/tools/wallet.ts +60 -24
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
type WalletEntry = {
|
|
5
|
+
id: string;
|
|
6
|
+
keyType: "evm" | "ows";
|
|
7
|
+
key?: string;
|
|
8
|
+
owsWalletId?: string;
|
|
9
|
+
chains: string[];
|
|
10
|
+
defaultChain?: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type CardConfig = {
|
|
15
|
+
consumerToken: string;
|
|
16
|
+
paymentMethodId?: string;
|
|
17
|
+
last4: string;
|
|
18
|
+
brand: string;
|
|
19
|
+
} | null;
|
|
20
|
+
|
|
21
|
+
type WalletToolResult = {
|
|
22
|
+
content: Array<{ type: "text"; text: string }>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const state = vi.hoisted(() => ({
|
|
26
|
+
wallets: [] as WalletEntry[],
|
|
27
|
+
addedWallets: [] as WalletEntry[],
|
|
28
|
+
card: null as CardConfig,
|
|
29
|
+
pendingCardSetupToken: null as string | null,
|
|
30
|
+
spendPolicies: {} as Record<string, unknown>,
|
|
31
|
+
consumerPrincipal: "did:pkh:eip155:8453:0xabc",
|
|
32
|
+
owsAvailable: true,
|
|
33
|
+
owsWallets: [] as Array<{ id: string; name: string; address: string }>,
|
|
34
|
+
owsWalletsByChain: [] as Array<{ id: string; name: string; address: string }>,
|
|
35
|
+
createdWalletCalls: [] as Array<{ name: string; chain: "evm" | "solana" }>,
|
|
36
|
+
importWalletCalls: [] as Array<{ privateKey: string; name: string; chain: "evm" | "solana" }>,
|
|
37
|
+
createdWalletResult: {
|
|
38
|
+
walletId: "ows-wallet-created",
|
|
39
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
40
|
+
},
|
|
41
|
+
importWalletResult: {
|
|
42
|
+
walletId: "ows-wallet-imported",
|
|
43
|
+
address: "0x2222222222222222222222222222222222222222",
|
|
44
|
+
},
|
|
45
|
+
cardSetup: {
|
|
46
|
+
url: "https://api.agentwonderland.com/card/handoff/setup-token",
|
|
47
|
+
token: "setup-token",
|
|
48
|
+
isNew: true,
|
|
49
|
+
},
|
|
50
|
+
cardSetupBlocks: [
|
|
51
|
+
"Open this setup page to connect your card:\n\nhttps://api.agentwonderland.com/card/handoff/setup-token",
|
|
52
|
+
],
|
|
53
|
+
pollCardSetupCalls: [] as Array<{ token: string; timeoutMs: number }>,
|
|
54
|
+
pollCardSetupResult: null as
|
|
55
|
+
| {
|
|
56
|
+
brand: string;
|
|
57
|
+
last4: string;
|
|
58
|
+
consumerToken: string;
|
|
59
|
+
}
|
|
60
|
+
| null,
|
|
61
|
+
setCardConfigCalls: [] as CardConfig[],
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
vi.mock("../../core/config.js", () => ({
|
|
65
|
+
addWallet: (wallet: WalletEntry) => {
|
|
66
|
+
state.addedWallets.push(wallet);
|
|
67
|
+
const existing = state.wallets.findIndex((entry) => entry.id === wallet.id);
|
|
68
|
+
if (existing >= 0) {
|
|
69
|
+
state.wallets[existing] = wallet;
|
|
70
|
+
} else {
|
|
71
|
+
state.wallets.push(wallet);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
getCardConfig: () => state.card,
|
|
75
|
+
getPendingCardSetupToken: () => state.pendingCardSetupToken,
|
|
76
|
+
getSpendPolicy: (walletId: string) => state.spendPolicies[walletId] ?? null,
|
|
77
|
+
getWallets: () => state.wallets,
|
|
78
|
+
setCardConfig: (card: CardConfig) => {
|
|
79
|
+
state.card = card;
|
|
80
|
+
state.setCardConfigCalls.push(card);
|
|
81
|
+
},
|
|
82
|
+
setSpendPolicy: (walletId: string, policy: unknown) => {
|
|
83
|
+
state.spendPolicies[walletId] = policy;
|
|
84
|
+
},
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
vi.mock("../../core/payments.js", () => ({
|
|
88
|
+
getWalletAddress: async () => null,
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
vi.mock("../../core/card-setup.js", () => ({
|
|
92
|
+
formatCardSetupBlocks: () => state.cardSetupBlocks,
|
|
93
|
+
getCardCapabilities: async () => ({
|
|
94
|
+
spt_status: "enabled",
|
|
95
|
+
}),
|
|
96
|
+
getOrCreatePendingCardSetup: async () => state.cardSetup,
|
|
97
|
+
pollCardSetup: async (token: string, timeoutMs: number) => {
|
|
98
|
+
state.pollCardSetupCalls.push({ token, timeoutMs });
|
|
99
|
+
return state.pollCardSetupResult;
|
|
100
|
+
},
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
vi.mock("../../core/ows-adapter.js", () => ({
|
|
104
|
+
createOwsWallet: async (name: string, chain: "evm" | "solana") => {
|
|
105
|
+
state.createdWalletCalls.push({ name, chain });
|
|
106
|
+
return state.createdWalletResult;
|
|
107
|
+
},
|
|
108
|
+
importKeyToOws: async (privateKey: string, name: string, chain: "evm" | "solana") => {
|
|
109
|
+
state.importWalletCalls.push({ privateKey, name, chain });
|
|
110
|
+
return state.importWalletResult;
|
|
111
|
+
},
|
|
112
|
+
isOwsAvailable: async () => state.owsAvailable,
|
|
113
|
+
listOwsWallets: async () => state.owsWallets,
|
|
114
|
+
listOwsWalletsByChain: async () => state.owsWalletsByChain,
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
vi.mock("../../core/principal.js", () => ({
|
|
118
|
+
ensureConsumerPrincipal: async () => state.consumerPrincipal,
|
|
119
|
+
getConsumerPrincipal: async () => state.consumerPrincipal,
|
|
120
|
+
}));
|
|
121
|
+
|
|
122
|
+
function resetState(): void {
|
|
123
|
+
state.wallets = [];
|
|
124
|
+
state.addedWallets = [];
|
|
125
|
+
state.card = null;
|
|
126
|
+
state.pendingCardSetupToken = null;
|
|
127
|
+
state.spendPolicies = {};
|
|
128
|
+
state.consumerPrincipal = "did:pkh:eip155:8453:0xabc";
|
|
129
|
+
state.owsAvailable = true;
|
|
130
|
+
state.owsWallets = [];
|
|
131
|
+
state.owsWalletsByChain = [];
|
|
132
|
+
state.createdWalletCalls = [];
|
|
133
|
+
state.importWalletCalls = [];
|
|
134
|
+
state.createdWalletResult = {
|
|
135
|
+
walletId: "ows-wallet-created",
|
|
136
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
137
|
+
};
|
|
138
|
+
state.importWalletResult = {
|
|
139
|
+
walletId: "ows-wallet-imported",
|
|
140
|
+
address: "0x2222222222222222222222222222222222222222",
|
|
141
|
+
};
|
|
142
|
+
state.cardSetup = {
|
|
143
|
+
url: "https://api.agentwonderland.com/card/handoff/setup-token",
|
|
144
|
+
token: "setup-token",
|
|
145
|
+
isNew: true,
|
|
146
|
+
};
|
|
147
|
+
state.cardSetupBlocks = [
|
|
148
|
+
"Open this setup page to connect your card:\n\nhttps://api.agentwonderland.com/card/handoff/setup-token",
|
|
149
|
+
];
|
|
150
|
+
state.pollCardSetupCalls = [];
|
|
151
|
+
state.pollCardSetupResult = null;
|
|
152
|
+
state.setCardConfigCalls = [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function getWalletSetupTool(): Promise<(args: Record<string, unknown>) => Promise<WalletToolResult>> {
|
|
156
|
+
const tools = new Map<string, (args: Record<string, unknown>) => Promise<WalletToolResult>>();
|
|
157
|
+
const server = {
|
|
158
|
+
tool: (
|
|
159
|
+
name: string,
|
|
160
|
+
_description: string,
|
|
161
|
+
_schema: unknown,
|
|
162
|
+
handler: (args: Record<string, unknown>) => Promise<WalletToolResult>,
|
|
163
|
+
) => {
|
|
164
|
+
tools.set(name, handler);
|
|
165
|
+
},
|
|
166
|
+
} as unknown as McpServer;
|
|
167
|
+
|
|
168
|
+
const { registerWalletTools } = await import("../wallet.js");
|
|
169
|
+
registerWalletTools(server);
|
|
170
|
+
|
|
171
|
+
const walletSetup = tools.get("wallet_setup");
|
|
172
|
+
if (!walletSetup) {
|
|
173
|
+
throw new Error("wallet_setup tool was not registered");
|
|
174
|
+
}
|
|
175
|
+
return walletSetup;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function flattenText(result: WalletToolResult): string {
|
|
179
|
+
return result.content.map((item) => item.text).join("\n");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
describe("wallet_setup tool", () => {
|
|
183
|
+
beforeEach(() => {
|
|
184
|
+
vi.resetModules();
|
|
185
|
+
resetState();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("creates an encrypted OWS wallet for Tempo/Base with Base as the default chain", async () => {
|
|
189
|
+
const walletSetup = await getWalletSetupTool();
|
|
190
|
+
|
|
191
|
+
const result = await walletSetup({ action: "create", name: "launch-wallet", chain: "base" });
|
|
192
|
+
const text = flattenText(result);
|
|
193
|
+
|
|
194
|
+
expect(state.createdWalletCalls).toEqual([
|
|
195
|
+
{ name: "launch-wallet", chain: "evm" },
|
|
196
|
+
]);
|
|
197
|
+
expect(state.addedWallets).toEqual([
|
|
198
|
+
{
|
|
199
|
+
id: "launch-wallet",
|
|
200
|
+
keyType: "ows",
|
|
201
|
+
owsWalletId: "ows-wallet-created",
|
|
202
|
+
chains: ["tempo", "base"],
|
|
203
|
+
defaultChain: "base",
|
|
204
|
+
label: "launch-wallet",
|
|
205
|
+
},
|
|
206
|
+
]);
|
|
207
|
+
expect(text).toContain("Wallet created [encrypted]:");
|
|
208
|
+
expect(text).toContain("Address: 0x1111111111111111111111111111111111111111");
|
|
209
|
+
expect(text).toContain("Chains: tempo, base");
|
|
210
|
+
expect(text).toContain("Consumer principal: did:pkh:eip155:8453:0xabc");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("imports a wallet into OWS encrypted storage with Base as the default chain", async () => {
|
|
214
|
+
const walletSetup = await getWalletSetupTool();
|
|
215
|
+
|
|
216
|
+
const result = await walletSetup({
|
|
217
|
+
action: "import",
|
|
218
|
+
key: "0x1234",
|
|
219
|
+
name: "imported-wallet",
|
|
220
|
+
chain: "base",
|
|
221
|
+
});
|
|
222
|
+
const text = flattenText(result);
|
|
223
|
+
|
|
224
|
+
expect(state.importWalletCalls).toEqual([
|
|
225
|
+
{ privateKey: "0x1234", name: "imported-wallet", chain: "evm" },
|
|
226
|
+
]);
|
|
227
|
+
expect(state.addedWallets).toEqual([
|
|
228
|
+
{
|
|
229
|
+
id: "imported-wallet",
|
|
230
|
+
keyType: "ows",
|
|
231
|
+
owsWalletId: "ows-wallet-imported",
|
|
232
|
+
chains: ["tempo", "base"],
|
|
233
|
+
defaultChain: "base",
|
|
234
|
+
label: "imported-wallet",
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
expect(text).toContain("Key imported to OWS [encrypted]:");
|
|
238
|
+
expect(text).toContain("Address: 0x2222222222222222222222222222222222222222");
|
|
239
|
+
expect(text).toContain("Consumer principal: did:pkh:eip155:8453:0xabc");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("starts card setup when no card is connected", async () => {
|
|
243
|
+
const walletSetup = await getWalletSetupTool();
|
|
244
|
+
|
|
245
|
+
const result = await walletSetup({ action: "add-card" });
|
|
246
|
+
const text = flattenText(result);
|
|
247
|
+
|
|
248
|
+
expect(text).toContain("Open this setup page to connect your card:");
|
|
249
|
+
expect(text).toContain("https://api.agentwonderland.com/card/handoff/setup-token");
|
|
250
|
+
expect(state.pollCardSetupCalls).toEqual([]);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("completes a pending card setup when Stripe handoff has finished", async () => {
|
|
254
|
+
state.pendingCardSetupToken = "setup-token";
|
|
255
|
+
state.pollCardSetupResult = {
|
|
256
|
+
brand: "Visa",
|
|
257
|
+
last4: "4242",
|
|
258
|
+
consumerToken: "consumer-token",
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const walletSetup = await getWalletSetupTool();
|
|
262
|
+
const result = await walletSetup({ action: "add-card" });
|
|
263
|
+
const text = flattenText(result);
|
|
264
|
+
|
|
265
|
+
expect(state.pollCardSetupCalls).toEqual([
|
|
266
|
+
{ token: "setup-token", timeoutMs: 250 },
|
|
267
|
+
]);
|
|
268
|
+
expect(text).toContain("Connected! Visa ****4242 is ready for payments.");
|
|
269
|
+
expect(text).toContain("Consumer principal: did:pkh:eip155:8453:0xabc");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("removes the connected card", async () => {
|
|
273
|
+
state.card = {
|
|
274
|
+
consumerToken: "consumer-token",
|
|
275
|
+
paymentMethodId: "pm_123",
|
|
276
|
+
last4: "4242",
|
|
277
|
+
brand: "Visa",
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const walletSetup = await getWalletSetupTool();
|
|
281
|
+
const result = await walletSetup({ action: "remove-card" });
|
|
282
|
+
const text = flattenText(result);
|
|
283
|
+
|
|
284
|
+
expect(state.setCardConfigCalls).toEqual([null]);
|
|
285
|
+
expect(state.card).toBeNull();
|
|
286
|
+
expect(text).toContain("Removed Visa ****4242.");
|
|
287
|
+
expect(text).toContain("Card disconnected from Agent Wonderland.");
|
|
288
|
+
});
|
|
289
|
+
});
|
|
@@ -47,6 +47,6 @@ export function formatPaymentChoicePrompt(
|
|
|
47
47
|
...commands,
|
|
48
48
|
"",
|
|
49
49
|
`Available methods: ${methods.map((method) => `"${method}"`).join(", ")}`,
|
|
50
|
-
"
|
|
50
|
+
"You can omit pay_with to use the default compatible method.",
|
|
51
51
|
].join("\n");
|
|
52
52
|
}
|
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}`);
|
package/src/tools/jobs.ts
CHANGED
|
@@ -18,7 +18,16 @@ export function registerJobTools(server: McpServer): void {
|
|
|
18
18
|
job_id: z.string().describe("Job ID (UUID)"),
|
|
19
19
|
},
|
|
20
20
|
async ({ job_id }) => {
|
|
21
|
-
|
|
21
|
+
let url = `/jobs/${job_id}`;
|
|
22
|
+
|
|
23
|
+
if (!isAuthenticated() && hasWalletConfigured()) {
|
|
24
|
+
const address = await getWalletAddress();
|
|
25
|
+
if (address) {
|
|
26
|
+
url += `?wallet=${encodeURIComponent(address)}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const result = await apiGet<Record<string, unknown>>(url);
|
|
22
31
|
if (result.status === "processing") {
|
|
23
32
|
return text(`Job ${job_id} is still processing...`);
|
|
24
33
|
}
|
|
@@ -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
|
@@ -4,6 +4,7 @@ import { apiGet, apiPostWithPayment } from "../core/api-client.js";
|
|
|
4
4
|
import {
|
|
5
5
|
formatCreditPack,
|
|
6
6
|
formatCreditPackOffer,
|
|
7
|
+
getCreditPackInventory,
|
|
7
8
|
getCreditPackProgram,
|
|
8
9
|
} from "../core/passes.js";
|
|
9
10
|
import {
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
normalizePaymentMethod,
|
|
14
15
|
} from "../core/payments.js";
|
|
15
16
|
import { requiresSpendConfirmation } from "../core/config.js";
|
|
16
|
-
import {
|
|
17
|
+
import { ensureConsumerPrincipalForMethod } from "../core/principal.js";
|
|
17
18
|
import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
|
|
18
19
|
import {
|
|
19
20
|
formatPaymentChoicePrompt,
|
|
@@ -84,7 +85,6 @@ export function registerPassTools(server: McpServer): void {
|
|
|
84
85
|
|
|
85
86
|
const agent = await getAgent(agent_id);
|
|
86
87
|
const agentName = agent.name ?? agent_id;
|
|
87
|
-
const principal = await ensureConsumerPrincipal();
|
|
88
88
|
const program = getCreditPackProgram(agent);
|
|
89
89
|
const offers = (program?.packs ?? [])
|
|
90
90
|
.map((pack) => findOffer(agent, pack.key ?? ""))
|
|
@@ -155,7 +155,7 @@ export function registerPassTools(server: McpServer): void {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
|
|
158
|
-
|
|
158
|
+
const principal = await ensureConsumerPrincipalForMethod(method);
|
|
159
159
|
if (requiresSpendConfirmation() && !confirmed) {
|
|
160
160
|
pendingCreditPackPurchases.set(agent.id, {
|
|
161
161
|
agentId: agent.id,
|
|
@@ -182,12 +182,7 @@ export function registerPassTools(server: McpServer): void {
|
|
|
182
182
|
consumer_principal: string;
|
|
183
183
|
offer: CreditPackOffer;
|
|
184
184
|
credit_pack: CreditPackRecord;
|
|
185
|
-
}>(
|
|
186
|
-
`/agents/${agent.id}/credit-packs/purchase`,
|
|
187
|
-
{ pack_id: resolvedOffer.pack_id },
|
|
188
|
-
method,
|
|
189
|
-
{ ensureConsumerPrincipal: true },
|
|
190
|
-
);
|
|
185
|
+
}>(`/agents/${agent.id}/credit-packs/purchase`, { pack_id: resolvedOffer.pack_id }, method, { ensureConsumerPrincipal: true });
|
|
191
186
|
|
|
192
187
|
pendingCreditPackPurchases.delete(agent.id);
|
|
193
188
|
|
|
@@ -198,7 +193,8 @@ export function registerPassTools(server: McpServer): void {
|
|
|
198
193
|
formatCreditPack(result.credit_pack),
|
|
199
194
|
`Consumer principal: ${result.consumer_principal}`,
|
|
200
195
|
"",
|
|
201
|
-
"Future runs
|
|
196
|
+
"Future runs for this agent will automatically use this credit pack while units remain.",
|
|
197
|
+
"That includes run_agent, and solve whenever it selects this same agent.",
|
|
202
198
|
].join("\n"));
|
|
203
199
|
},
|
|
204
200
|
);
|
|
@@ -208,10 +204,14 @@ export function registerPassTools(server: McpServer): void {
|
|
|
208
204
|
"Show discounted credit-pack offers for an agent plus any balances available under the current consumer principal.",
|
|
209
205
|
{
|
|
210
206
|
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
207
|
+
pay_with: z.string().optional().describe("Optional payment method context used to inspect the matching consumer principal."),
|
|
211
208
|
},
|
|
212
|
-
async ({ agent_id }) => {
|
|
209
|
+
async ({ agent_id, pay_with }) => {
|
|
213
210
|
const agent = await getAgent(agent_id);
|
|
214
|
-
const result = await
|
|
211
|
+
const result = await getCreditPackInventory(agent.id, pay_with);
|
|
212
|
+
if (!result) {
|
|
213
|
+
return text(`Could not load credit-pack inventory for ${agent.name}.`);
|
|
214
|
+
}
|
|
215
215
|
|
|
216
216
|
const lines = [
|
|
217
217
|
`Credit packs for ${agent.name}`,
|
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,
|
|
@@ -32,9 +32,10 @@ const POLL_MAX_MS = 120000;
|
|
|
32
32
|
|
|
33
33
|
async function pollJobUntilDone(
|
|
34
34
|
jobId: string,
|
|
35
|
+
paymentMethod?: string,
|
|
35
36
|
): Promise<{ status: string; output?: unknown; error_code?: string }> {
|
|
36
37
|
const deadline = Date.now() + POLL_MAX_MS;
|
|
37
|
-
const walletAddress = await getWalletAddress();
|
|
38
|
+
const walletAddress = await getWalletAddress(paymentMethod);
|
|
38
39
|
const walletParam = walletAddress ? `?wallet=${walletAddress}` : "";
|
|
39
40
|
|
|
40
41
|
while (Date.now() < deadline) {
|
|
@@ -100,7 +101,7 @@ export function registerRunTools(server: McpServer): void {
|
|
|
100
101
|
{
|
|
101
102
|
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
102
103
|
input: z.record(z.unknown()).describe("Input payload for the agent"),
|
|
103
|
-
pay_with: z.string().optional().describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
|
|
104
|
+
pay_with: z.string().trim().min(1).optional().describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
|
|
104
105
|
confirmed: z.boolean().optional().describe("Set to true to confirm spending after seeing the price quote."),
|
|
105
106
|
},
|
|
106
107
|
async ({ agent_id, input, pay_with, confirmed }) => {
|
|
@@ -124,24 +125,23 @@ export function registerRunTools(server: McpServer): void {
|
|
|
124
125
|
return text(`Agent "${agent_id}" not found. Use search_agents to find available agents.`);
|
|
125
126
|
}
|
|
126
127
|
|
|
127
|
-
const price = parseFloat(agent.
|
|
128
|
+
const price = parseFloat(agent.pricePerRunUsd ?? "0.01");
|
|
128
129
|
const agentName = agent.name ?? agent_id;
|
|
129
|
-
const
|
|
130
|
-
const activeCreditPack = getActiveCreditPack(creditPackInventory);
|
|
131
|
-
const configuredMethods = getConfiguredMethods();
|
|
132
|
-
const compatibleMethods = getCompatiblePaymentMethods(agent, configuredMethods);
|
|
130
|
+
const compatibleMethods = getCompatiblePaymentMethods(agent, getConfiguredMethods());
|
|
133
131
|
const pending = pendingRuns.get(agent.id);
|
|
134
132
|
const requestedMethod = pay_with ?? pending?.method;
|
|
135
133
|
const normalizedRequestedMethod = requestedMethod ? normalizePaymentMethod(requestedMethod) : null;
|
|
134
|
+
const creditPackInventory = await getCreditPackInventory(agent.id, requestedMethod);
|
|
135
|
+
const activeCreditPack = getActiveCreditPack(creditPackInventory);
|
|
136
136
|
|
|
137
|
-
if (
|
|
137
|
+
if (requestedMethod && !normalizedRequestedMethod) {
|
|
138
138
|
return text(
|
|
139
139
|
`Payment method "${requestedMethod}" is not configured.\n\n` +
|
|
140
140
|
"Use wallet_status to review your current payment methods.",
|
|
141
141
|
);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
if (
|
|
144
|
+
if (normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
|
|
145
145
|
return text(
|
|
146
146
|
`This agent cannot be paid with "${requestedMethod}".\n\n` +
|
|
147
147
|
`Available payment methods for this agent: ${compatibleMethods.join(", ") || "none"}.\n` +
|
|
@@ -149,30 +149,28 @@ export function registerRunTools(server: McpServer): void {
|
|
|
149
149
|
);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
if (!activeCreditPack &&
|
|
152
|
+
if (!activeCreditPack && compatibleMethods.length === 0) {
|
|
153
153
|
return text(
|
|
154
154
|
`No compatible payment methods are configured for ${agentName}.\n\n` +
|
|
155
|
-
|
|
156
|
-
`Agent accepts: ${agent.payment?.accepted_payments?.join(", ") || "unknown"}\n` +
|
|
157
|
-
"Use wallet_status to review your current setup.",
|
|
155
|
+
"Use wallet_status to review your current payment methods.",
|
|
158
156
|
);
|
|
159
157
|
}
|
|
160
158
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
formatPaymentChoicePrompt(
|
|
164
|
-
agentName,
|
|
165
|
-
compatibleMethods,
|
|
166
|
-
compatibleMethods.map((method) => formatRunConfirmationCommand(agent.id, method).replace(", confirmed: true", "")),
|
|
167
|
-
),
|
|
168
|
-
);
|
|
169
|
-
}
|
|
159
|
+
const method = resolveConfirmationMethod(requestedMethod, pending?.method, compatibleMethods);
|
|
160
|
+
const spendCheckMethod = method ?? normalizedRequestedMethod ?? compatibleMethods[0];
|
|
170
161
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
162
|
+
if (!activeCreditPack) {
|
|
163
|
+
const spendCheck = canSpend({
|
|
164
|
+
method: spendCheckMethod,
|
|
165
|
+
amountUsd: price,
|
|
166
|
+
});
|
|
167
|
+
if (!spendCheck.ok) {
|
|
168
|
+
return text(spendCheck.message);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
174
171
|
|
|
175
|
-
|
|
172
|
+
const needsPolicyConfirmation = !activeCreditPack && requiresPolicyConfirmation(spendCheckMethod, price);
|
|
173
|
+
if (!activeCreditPack && (requiresSpendConfirmation() || needsPolicyConfirmation) && !confirmed) {
|
|
176
174
|
pendingRuns.set(agent.id, {
|
|
177
175
|
agent: { id: agent.id, name: agentName, price },
|
|
178
176
|
input,
|
|
@@ -218,26 +216,37 @@ export function registerRunTools(server: McpServer): void {
|
|
|
218
216
|
}
|
|
219
217
|
|
|
220
218
|
let result: Record<string, unknown>;
|
|
219
|
+
let usedPaidMethod = !activeCreditPack;
|
|
221
220
|
try {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
if (activeCreditPack) {
|
|
222
|
+
try {
|
|
223
|
+
result = await apiPost<Record<string, unknown>>(
|
|
224
|
+
`/agents/${agent.id}/run`,
|
|
225
|
+
{ input: processedInput },
|
|
226
|
+
{ ensureConsumerPrincipal: true },
|
|
227
|
+
);
|
|
228
|
+
} catch (packErr) {
|
|
229
|
+
const packApiErr = packErr as { status?: number };
|
|
230
|
+
if (packApiErr?.status !== 402) throw packErr;
|
|
231
|
+
result = await apiPostWithPayment<Record<string, unknown>>(
|
|
232
|
+
`/agents/${agent.id}/run`,
|
|
233
|
+
{ input: processedInput },
|
|
234
|
+
method,
|
|
235
|
+
);
|
|
236
|
+
usedPaidMethod = true;
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
result = await apiPostWithPayment<Record<string, unknown>>(
|
|
229
240
|
`/agents/${agent.id}/run`,
|
|
230
241
|
{ input: processedInput },
|
|
231
242
|
method,
|
|
232
243
|
);
|
|
244
|
+
}
|
|
245
|
+
if (usedPaidMethod) {
|
|
246
|
+
recordSpend(spendCheckMethod, price);
|
|
247
|
+
}
|
|
233
248
|
} catch (err: unknown) {
|
|
234
249
|
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
250
|
if (apiErr?.status === 402) {
|
|
242
251
|
const allMethods = getConfiguredMethods();
|
|
243
252
|
const methodName = method ?? allMethods[0] ?? "auto";
|
|
@@ -277,7 +286,7 @@ export function registerRunTools(server: McpServer): void {
|
|
|
277
286
|
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
278
287
|
|
|
279
288
|
if (status === "processing") {
|
|
280
|
-
const pollResult = await pollJobUntilDone(jobId);
|
|
289
|
+
const pollResult = await pollJobUntilDone(jobId, method);
|
|
281
290
|
if (pollResult.status === "completed") {
|
|
282
291
|
const asyncFormatted = formatRunResult({
|
|
283
292
|
...result,
|