@agentwonderland/mcp 0.1.25 → 0.1.27

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.
Files changed (78) hide show
  1. package/dist/core/__tests__/api-client.test.d.ts +1 -0
  2. package/dist/core/__tests__/api-client.test.js +51 -0
  3. package/dist/core/__tests__/formatters.test.js +10 -0
  4. package/dist/core/__tests__/passes-api.test.d.ts +1 -0
  5. package/dist/core/__tests__/passes-api.test.js +27 -0
  6. package/dist/core/__tests__/payments.test.js +10 -6
  7. package/dist/core/__tests__/principal.test.js +41 -4
  8. package/dist/core/__tests__/solana-charge.test.d.ts +1 -0
  9. package/dist/core/__tests__/solana-charge.test.js +50 -0
  10. package/dist/core/api-client.d.ts +1 -0
  11. package/dist/core/api-client.js +8 -3
  12. package/dist/core/balances.d.ts +1 -0
  13. package/dist/core/balances.js +56 -0
  14. package/dist/core/base-charge.js +13 -6
  15. package/dist/core/formatters.d.ts +3 -2
  16. package/dist/core/formatters.js +7 -1
  17. package/dist/core/passes.d.ts +1 -1
  18. package/dist/core/passes.js +5 -2
  19. package/dist/core/payments.d.ts +1 -0
  20. package/dist/core/payments.js +20 -7
  21. package/dist/core/principal.d.ts +3 -0
  22. package/dist/core/principal.js +29 -1
  23. package/dist/core/settings.d.ts +20 -0
  24. package/dist/core/settings.js +19 -0
  25. package/dist/core/solana-charge.d.ts +5 -0
  26. package/dist/core/solana-charge.js +29 -7
  27. package/dist/core/tempo-charge.d.ts +7 -0
  28. package/dist/core/tempo-charge.js +84 -0
  29. package/dist/index.js +5 -7
  30. package/dist/prompts/index.js +1 -1
  31. package/dist/tools/__tests__/jobs.test.d.ts +1 -0
  32. package/dist/tools/__tests__/jobs.test.js +71 -0
  33. package/dist/tools/__tests__/run.test.d.ts +1 -0
  34. package/dist/tools/__tests__/run.test.js +149 -0
  35. package/dist/tools/__tests__/solve.test.d.ts +1 -0
  36. package/dist/tools/__tests__/solve.test.js +158 -0
  37. package/dist/tools/__tests__/wallet.test.d.ts +1 -0
  38. package/dist/tools/__tests__/wallet.test.js +230 -0
  39. package/dist/tools/_payment-confirmation.js +1 -1
  40. package/dist/tools/agent-info.js +14 -28
  41. package/dist/tools/jobs.js +82 -16
  42. package/dist/tools/passes.js +30 -14
  43. package/dist/tools/run.js +35 -20
  44. package/dist/tools/search.js +9 -8
  45. package/dist/tools/solve.js +45 -25
  46. package/dist/tools/wallet.js +35 -15
  47. package/package.json +2 -2
  48. package/src/core/__tests__/api-client.test.ts +78 -0
  49. package/src/core/__tests__/formatters.test.ts +12 -0
  50. package/src/core/__tests__/passes-api.test.ts +33 -0
  51. package/src/core/__tests__/payments.test.ts +17 -6
  52. package/src/core/__tests__/principal.test.ts +49 -4
  53. package/src/core/__tests__/solana-charge.test.ts +59 -0
  54. package/src/core/api-client.ts +16 -3
  55. package/src/core/balances.ts +63 -0
  56. package/src/core/base-charge.ts +13 -6
  57. package/src/core/formatters.ts +10 -3
  58. package/src/core/passes.ts +5 -2
  59. package/src/core/payments.ts +22 -7
  60. package/src/core/principal.ts +42 -1
  61. package/src/core/settings.ts +36 -0
  62. package/src/core/solana-charge.ts +43 -9
  63. package/src/core/tempo-charge.ts +104 -0
  64. package/src/index.ts +5 -7
  65. package/src/prompts/index.ts +1 -1
  66. package/src/tools/__tests__/jobs.test.ts +89 -0
  67. package/src/tools/__tests__/run.test.ts +176 -0
  68. package/src/tools/__tests__/solve.test.ts +186 -0
  69. package/src/tools/__tests__/wallet.test.ts +289 -0
  70. package/src/tools/_payment-confirmation.ts +1 -1
  71. package/src/tools/agent-info.ts +15 -38
  72. package/src/tools/jobs.ts +79 -17
  73. package/src/tools/passes.ts +30 -14
  74. package/src/tools/run.ts +38 -20
  75. package/src/tools/search.ts +10 -9
  76. package/src/tools/solve.ts +48 -25
  77. package/src/tools/wallet.ts +33 -17
  78. package/src/tools/observability.ts +0 -43
@@ -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
- "For fully agentic execution, include pay_with explicitly.",
50
+ "You can omit pay_with to use the default compatible method.",
51
51
  ].join("\n");
52
52
  }
@@ -31,14 +31,27 @@ export function registerAgentInfoTools(server: McpServer): void {
31
31
  }>;
32
32
  } | null | undefined;
33
33
 
34
+ const totalJobs = (s.completedJobs ?? a.totalExecutions ?? 0) as number;
35
+ const acceptedPayments = (payment.accepted_payments as string[] | undefined) ?? [];
36
+ const paymentLabelMap: Record<string, string> = {
37
+ tempo_usdc: "tempo",
38
+ base_usdc: "base",
39
+ solana_usdc: "solana",
40
+ stripe_card: "card",
41
+ };
42
+ const paymentLabels = acceptedPayments.map((m) => paymentLabelMap[m] ?? m);
43
+
34
44
  const lines = [
35
45
  `${a.name}`,
36
- `${stars(a.avgRating ?? (s.avgRating as number))} (${s.ratingCount ?? 0} reviews) • ${compactNumber((s.completedJobs ?? a.totalExecutions ?? 0) as number)} jobs`,
46
+ `${stars(a.avgRating ?? (s.avgRating as number))} (${s.ratingCount ?? 0} reviews) • ${compactNumber(totalJobs)} jobs`,
37
47
  "",
38
48
  (a.description as string) ?? "",
39
49
  "",
40
50
  `Pricing: ${formatPrice(a.pricePerRunUsd)}`,
41
- `Reliability: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
51
+ ...(paymentLabels.length > 0 ? [`Accepted payments: ${paymentLabels.join(", ")}`] : []),
52
+ ...(totalJobs > 0 && a.successRate != null
53
+ ? [`Reliability: ${(Number(a.successRate) * 100).toFixed(0)}% (${compactNumber(totalJobs)} runs)`]
54
+ : []),
42
55
  `Avg latency: ${(a.avgResponseTimeMs as number) != null ? a.avgResponseTimeMs + "ms" : "N/A"}`,
43
56
  ...(() => {
44
57
  const lastActive = formatLastActive(a.lastActiveAt as string | null);
@@ -104,40 +117,4 @@ export function registerAgentInfoTools(server: McpServer): void {
104
117
  return text(lines.join("\n"));
105
118
  },
106
119
  );
107
-
108
- // ── compare_agents ──────────────────────────────────────────────
109
- server.tool(
110
- "compare_agents",
111
- "Compare multiple agents side-by-side on rating, price, success rate, and job count.",
112
- {
113
- agent_ids: z
114
- .array(z.string())
115
- .min(2)
116
- .max(5)
117
- .describe("Agent IDs to compare (2-5)"),
118
- },
119
- async ({ agent_ids }) => {
120
- const agents = await Promise.all(
121
- agent_ids.map((id) => apiGet<AgentRecord>(`/agents/${id}`)),
122
- );
123
-
124
- const header = "Agent Comparison:\n";
125
- const lines = agents.map((a) => {
126
- const s = (a.stats ?? {}) as Record<string, unknown>;
127
- const rating = a.avgRating ?? (s.avgRating as number);
128
- const jobs = (s.completedJobs ?? a.totalExecutions ?? 0) as number;
129
- const tipCount = (s.tipCount ?? 0) as number;
130
- return [
131
- ` ${a.name}`,
132
- ` ${stars(rating)} (${s.ratingCount ?? 0} reviews)${tipCount > 0 ? ` • ${tipCount} tips` : ""}`,
133
- ` ${compactNumber(jobs)} jobs • ${formatPrice(a.pricePerRunUsd)}`,
134
- ` Success: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
135
- ` ${agentWebUrl(a.id)}`,
136
- "",
137
- ].join("\n");
138
- });
139
-
140
- return text(header + lines.join("\n"));
141
- },
142
- );
143
120
  }
package/src/tools/jobs.ts CHANGED
@@ -9,6 +9,26 @@ function text(t: string) {
9
9
  return { content: [{ type: "text" as const, text: t }] };
10
10
  }
11
11
 
12
+ async function getConsumerWalletAddresses(): Promise<string[]> {
13
+ const addresses = new Set<string>();
14
+ for (const chain of ["tempo", "base", "solana"]) {
15
+ const addr = await getWalletAddress(chain);
16
+ if (addr) addresses.add(addr);
17
+ }
18
+ return [...addresses];
19
+ }
20
+
21
+ function formatRelativeTime(iso: string | null | undefined): string {
22
+ if (!iso) return "";
23
+ const t = new Date(iso).getTime();
24
+ if (!Number.isFinite(t)) return "";
25
+ const diffSec = Math.max(0, Math.floor((Date.now() - t) / 1000));
26
+ if (diffSec < 60) return `${diffSec}s ago`;
27
+ if (diffSec < 3600) return `${Math.floor(diffSec / 60)}m ago`;
28
+ if (diffSec < 86400) return `${Math.floor(diffSec / 3600)}h ago`;
29
+ return `${Math.floor(diffSec / 86400)}d ago`;
30
+ }
31
+
12
32
  export function registerJobTools(server: McpServer): void {
13
33
  // ── get_job ─────────────────────────────────────────────────────
14
34
  server.tool(
@@ -18,7 +38,25 @@ export function registerJobTools(server: McpServer): void {
18
38
  job_id: z.string().describe("Job ID (UUID)"),
19
39
  },
20
40
  async ({ job_id }) => {
21
- const result = await apiGet<Record<string, unknown>>(`/jobs/${job_id}`);
41
+ const walletLookup = !isAuthenticated() && hasWalletConfigured();
42
+ const addresses = walletLookup ? await getConsumerWalletAddresses() : [""];
43
+
44
+ let result: Record<string, unknown> | null = null;
45
+ for (const addr of addresses) {
46
+ const url = addr ? `/jobs/${job_id}?wallet=${encodeURIComponent(addr)}` : `/jobs/${job_id}`;
47
+ try {
48
+ result = await apiGet<Record<string, unknown>>(url);
49
+ break;
50
+ } catch (err) {
51
+ const status = (err as { status?: number }).status;
52
+ if (status !== 404) throw err;
53
+ }
54
+ }
55
+
56
+ if (!result) {
57
+ return text(`Job ${job_id} not found. Either the job ID is wrong or it was paid with a wallet not configured here.`);
58
+ }
59
+
22
60
  if (result.status === "processing") {
23
61
  return text(`Job ${job_id} is still processing...`);
24
62
  }
@@ -26,26 +64,46 @@ export function registerJobTools(server: McpServer): void {
26
64
  },
27
65
  );
28
66
 
29
- // ── list_jobs (renamed from list_my_jobs) ───────────────────────
67
+ // ── list_jobs ───────────────────────────────────────────────────
30
68
  server.tool(
31
69
  "list_jobs",
32
- "List your recent jobs with status, cost, and agent info.",
70
+ "List your recent jobs across all configured payment wallets, with status, cost, agent, method, and age.",
33
71
  {
34
- limit: z.coerce.number().optional().default(10).describe("Max results (1-50)"),
72
+ limit: z.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results (1-50)"),
35
73
  },
36
74
  async ({ limit }) => {
37
- let url = `/jobs?limit=${limit ?? 10}`;
75
+ const requestedLimit = limit ?? 10;
76
+ const walletLookup = !isAuthenticated() && hasWalletConfigured();
77
+ const addresses = walletLookup ? await getConsumerWalletAddresses() : [""];
38
78
 
39
- // If not authenticated via API key, use wallet address for lookup
40
- if (!isAuthenticated() && hasWalletConfigured()) {
41
- const address = await getWalletAddress();
42
- if (address) {
43
- url += `&wallet=${encodeURIComponent(address)}`;
79
+ const seen = new Set<string>();
80
+ const collected: Array<Record<string, unknown>> = [];
81
+ for (const addr of addresses) {
82
+ const url = addr
83
+ ? `/jobs?wallet=${encodeURIComponent(addr)}&limit=${requestedLimit}`
84
+ : `/jobs?limit=${requestedLimit}`;
85
+ try {
86
+ const jobs = await apiGet<Array<Record<string, unknown>>>(url);
87
+ for (const j of jobs) {
88
+ const id = j.job_id as string;
89
+ if (!seen.has(id)) {
90
+ seen.add(id);
91
+ collected.push(j);
92
+ }
93
+ }
94
+ } catch {
95
+ // Skip address that errored; continue with others.
44
96
  }
45
97
  }
46
98
 
47
- const jobs = await apiGet<Array<Record<string, unknown>>>(url);
48
- if (jobs.length === 0) return text("No jobs found.");
99
+ if (collected.length === 0) return text("No jobs found.");
100
+
101
+ collected.sort((a, b) => {
102
+ const aTime = new Date((a.created_at as string) ?? 0).getTime();
103
+ const bTime = new Date((b.created_at as string) ?? 0).getTime();
104
+ return bTime - aTime;
105
+ });
106
+ const jobs = collected.slice(0, requestedLimit);
49
107
 
50
108
  const lines = [`Recent jobs (${jobs.length}):`];
51
109
  for (const j of jobs) {
@@ -55,12 +113,16 @@ export function registerJobTools(server: McpServer): void {
55
113
  : j.status === "processing"
56
114
  ? "\u2026"
57
115
  : "\u2717";
58
- const cost =
59
- j.estimated_cost != null
60
- ? `$${Number(j.estimated_cost).toFixed(4)}`
61
- : "";
116
+ const amount = (j.settled_amount ?? j.estimated_cost) as string | number | undefined;
117
+ const cost = amount != null ? `$${Number(amount).toFixed(4)}` : "";
118
+ const method = ((j.settlement_trace as Record<string, unknown> | undefined)
119
+ ?.payment_attempt as { payment_method?: string } | undefined)?.payment_method;
120
+ const methodLabel = method ? `[${method.replace(/_usdc$/, "").replace("stripe_", "")}]` : "";
121
+ const shortId = (j.job_id as string)?.slice(0, 8);
122
+ const shortAgent = j.agent_id ? String(j.agent_id).slice(0, 8) : "?";
123
+ const when = formatRelativeTime(j.created_at as string | null);
62
124
  lines.push(
63
- ` ${status} ${(j.job_id as string)?.slice(0, 8)}\u2026 ${j.agent_id ? String(j.agent_id).slice(0, 8) + "\u2026" : ""} ${cost}`,
125
+ ` ${status} ${shortId} agent=${shortAgent} ${cost} ${methodLabel} ${when}`.trimEnd(),
64
126
  );
65
127
  }
66
128
  return text(lines.join("\n"));
@@ -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 {
@@ -11,9 +12,10 @@ import {
11
12
  getConfiguredMethods,
12
13
  hasWalletConfigured,
13
14
  normalizePaymentMethod,
15
+ isCardPaymentEnabled,
14
16
  } from "../core/payments.js";
15
17
  import { requiresSpendConfirmation } from "../core/config.js";
16
- import { ensureConsumerPrincipal } from "../core/principal.js";
18
+ import { ensureConsumerPrincipalForMethod } from "../core/principal.js";
17
19
  import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
18
20
  import {
19
21
  formatPaymentChoicePrompt,
@@ -70,21 +72,29 @@ export function registerPassTools(server: McpServer): void {
70
72
  },
71
73
  async ({ agent_id, pack_id, pay_with, confirmed }) => {
72
74
  if (!hasWalletConfigured()) {
73
- try {
74
- const { url } = await getOrCreatePendingCardSetup();
75
- return multiText(...formatCardSetupBlocks(url));
76
- } catch {
77
- return text(
78
- "No payment method configured.\n\n" +
79
- "To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
80
- "To use crypto: wallet_setup({ action: \"create\" })",
81
- );
75
+ if (isCardPaymentEnabled()) {
76
+ try {
77
+ const { url } = await getOrCreatePendingCardSetup();
78
+ return multiText(...formatCardSetupBlocks(url));
79
+ } catch {
80
+ // Fall through to the setup message below.
81
+ }
82
82
  }
83
+ const setupLines = [
84
+ "No payment method configured.",
85
+ "",
86
+ "Supported rails: Tempo USDC, Base USDC, Solana USDC.",
87
+ "Run wallet_setup({ action: \"create\" }) to create a crypto wallet,",
88
+ "or wallet_setup({ action: \"import\" }) with an existing key.",
89
+ ];
90
+ if (isCardPaymentEnabled()) {
91
+ setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
92
+ }
93
+ return text(setupLines.join("\n"));
83
94
  }
84
95
 
85
96
  const agent = await getAgent(agent_id);
86
97
  const agentName = agent.name ?? agent_id;
87
- const principal = await ensureConsumerPrincipal();
88
98
  const program = getCreditPackProgram(agent);
89
99
  const offers = (program?.packs ?? [])
90
100
  .map((pack) => findOffer(agent, pack.key ?? ""))
@@ -155,6 +165,7 @@ export function registerPassTools(server: McpServer): void {
155
165
  }
156
166
 
157
167
  const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
168
+ const principal = await ensureConsumerPrincipalForMethod(method);
158
169
  if (requiresSpendConfirmation() && !confirmed) {
159
170
  pendingCreditPackPurchases.set(agent.id, {
160
171
  agentId: agent.id,
@@ -192,7 +203,8 @@ export function registerPassTools(server: McpServer): void {
192
203
  formatCreditPack(result.credit_pack),
193
204
  `Consumer principal: ${result.consumer_principal}`,
194
205
  "",
195
- "Future runs through run_agent will automatically use this credit pack while units remain.",
206
+ "Future runs for this agent will automatically use this credit pack while units remain.",
207
+ "That includes run_agent, and solve whenever it selects this same agent.",
196
208
  ].join("\n"));
197
209
  },
198
210
  );
@@ -202,10 +214,14 @@ export function registerPassTools(server: McpServer): void {
202
214
  "Show discounted credit-pack offers for an agent plus any balances available under the current consumer principal.",
203
215
  {
204
216
  agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
217
+ pay_with: z.string().optional().describe("Optional payment method context used to inspect the matching consumer principal."),
205
218
  },
206
- async ({ agent_id }) => {
219
+ async ({ agent_id, pay_with }) => {
207
220
  const agent = await getAgent(agent_id);
208
- const result = await apiGet<CreditPackInventory>(`/agents/${agent.id}/credit-packs`, { ensureConsumerPrincipal: true });
221
+ const result = await getCreditPackInventory(agent.id, pay_with);
222
+ if (!result) {
223
+ return text(`Could not load credit-pack inventory for ${agent.name}.`);
224
+ }
209
225
 
210
226
  const lines = [
211
227
  `Credit packs for ${agent.name}`,