@agentwonderland/mcp 0.1.26 → 0.1.28
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/index.js +29 -30
- package/dist/prompts/index.js +1 -1
- package/dist/tools/agent-info.js +14 -28
- package/dist/tools/jobs.js +80 -21
- package/dist/tools/passes.js +19 -8
- package/dist/tools/run.js +19 -8
- package/dist/tools/search.js +9 -8
- package/dist/tools/solve.js +23 -10
- package/dist/tools/wallet.js +4 -4
- package/package.json +1 -1
- package/src/index.ts +29 -30
- package/src/prompts/index.ts +1 -1
- package/src/tools/agent-info.ts +15 -38
- package/src/tools/jobs.ts +75 -22
- package/src/tools/passes.ts +19 -9
- package/src/tools/run.ts +19 -9
- package/src/tools/search.ts +10 -9
- package/src/tools/solve.ts +23 -11
- package/src/tools/wallet.ts +4 -4
- package/src/tools/observability.ts +0 -43
package/dist/index.js
CHANGED
|
@@ -12,7 +12,6 @@ import { registerWalletTools } from "./tools/wallet.js";
|
|
|
12
12
|
import { registerFavoriteTools } from "./tools/favorites.js";
|
|
13
13
|
import { registerTipTools } from "./tools/tip.js";
|
|
14
14
|
import { registerPassTools } from "./tools/passes.js";
|
|
15
|
-
import { registerObservabilityTools } from "./tools/observability.js";
|
|
16
15
|
// ── Resources ────────────────────────────────────────────────────
|
|
17
16
|
import { registerAgentResources } from "./resources/agents.js";
|
|
18
17
|
import { registerWalletResources } from "./resources/wallet.js";
|
|
@@ -25,41 +24,42 @@ export async function startMcpServer() {
|
|
|
25
24
|
version: "0.2.0",
|
|
26
25
|
}, {
|
|
27
26
|
instructions: [
|
|
28
|
-
"Agent Wonderland — AI agent marketplace. Discover, run,
|
|
27
|
+
"Agent Wonderland — on-demand AI agent marketplace with autonomous per-call payment. Discover, run, rate.",
|
|
29
28
|
"",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
29
|
+
"PURPOSE:",
|
|
30
|
+
"You are an AI agent calling this MCP to extend your capabilities. Instead of integrating N APIs with N keys,",
|
|
31
|
+
"you pay specialist agents per call in USDC. This works headlessly in automations (n8n, cron) and interactively",
|
|
32
|
+
"(Claude Code, Desktop). Favor one-shot `solve()` for autonomous use; prefer `search_agents()` + `run_agent()`",
|
|
33
|
+
"when the caller wants to pick a specific agent.",
|
|
33
34
|
"",
|
|
34
35
|
"WORKFLOW:",
|
|
35
|
-
"1.
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"4.
|
|
40
|
-
"5. Ask user to rate or tip after a successful run",
|
|
36
|
+
"1. solve(intent, input, budget) — one call: find best agent + pay + run. Use when the task is clear.",
|
|
37
|
+
" OR: search_agents() → get_agent() to inspect schema → run_agent() with required fields.",
|
|
38
|
+
"2. If the agent returns status 'processing', poll with get_job(). Async runs resolve automatically.",
|
|
39
|
+
"3. After a successful run, rate_agent() and optionally tip_agent() if the result was useful.",
|
|
40
|
+
"4. Use list_jobs() to recover state across sessions (it checks every configured wallet).",
|
|
41
41
|
"",
|
|
42
|
-
"PAYMENT:",
|
|
43
|
-
"-
|
|
44
|
-
"-
|
|
45
|
-
"-
|
|
46
|
-
"
|
|
47
|
-
"-
|
|
48
|
-
"-
|
|
49
|
-
"-
|
|
50
|
-
"- Use open_observability_dashboard() to open a secure web usage dashboard for runs/spend/rebates.",
|
|
51
|
-
"- Payment is automatic once configured. Users are never charged for failed runs.",
|
|
52
|
-
"- Do NOT ask the user to set up payment before they try to run an agent. Let them explore freely.",
|
|
53
|
-
"- If a specific payment method fails, report the error clearly. Do NOT silently fall back to a different method.",
|
|
54
|
-
"- When payment fails, suggest alternatives using wallet_status.",
|
|
42
|
+
"PAYMENT (crypto-only right now):",
|
|
43
|
+
"- Supported rails: Tempo USDC, Base USDC, Solana USDC. Card is temporarily disabled pending Stripe SPT approval.",
|
|
44
|
+
"- Tempo and Base share one EVM wallet key. Solana uses a separate ed25519 key. One OWS wallet can manage both.",
|
|
45
|
+
"- If pay_with is omitted, the MCP auto-selects a compatible configured rail. Pass pay_with explicitly",
|
|
46
|
+
" (tempo | base | solana | wallet-id) for deterministic behavior.",
|
|
47
|
+
"- Payment is automatic: on a 402 challenge the MCP signs on-chain, submits, then retries. Failed runs are refunded.",
|
|
48
|
+
"- If a specific rail fails, surface the real reason — do NOT silently retry with a different method.",
|
|
49
|
+
"- Headless/automation: set wallet_set_policy() to cap max_per_tx and max_per_day so a runaway loop can't drain funds.",
|
|
55
50
|
"",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
51
|
+
"FILE HANDLING:",
|
|
52
|
+
"Pass local file paths directly as input values (e.g. image: \"/path/to/photo.jpg\"). The MCP auto-uploads to",
|
|
53
|
+
"cloud storage and replaces with a URL before sending to the agent. Never base64-encode.",
|
|
59
54
|
"",
|
|
60
55
|
"REQUIRED FIELDS:",
|
|
61
|
-
"Always
|
|
62
|
-
"
|
|
56
|
+
"Always inspect the agent's input schema via get_agent() before calling run_agent(). Include ALL required",
|
|
57
|
+
"fields in the first attempt — validation errors cost a round trip.",
|
|
58
|
+
"",
|
|
59
|
+
"WALLET HYGIENE:",
|
|
60
|
+
"- wallet_status shows per-chain USDC balance and the active network (mainnet vs testnet).",
|
|
61
|
+
"- To create or import a crypto wallet: wallet_setup({ action: \"create\" }) or { action: \"import\", key }.",
|
|
62
|
+
"- NEVER delete or rotate keys programmatically. Direct users to edit ~/.agentwonderland/config.json or ~/.ows/ manually.",
|
|
63
63
|
].join("\n"),
|
|
64
64
|
});
|
|
65
65
|
// Register tools
|
|
@@ -73,7 +73,6 @@ export async function startMcpServer() {
|
|
|
73
73
|
registerFavoriteTools(server);
|
|
74
74
|
registerTipTools(server);
|
|
75
75
|
registerPassTools(server);
|
|
76
|
-
registerObservabilityTools(server);
|
|
77
76
|
// Register resources
|
|
78
77
|
registerAgentResources(server);
|
|
79
78
|
registerWalletResources(server);
|
package/dist/prompts/index.js
CHANGED
|
@@ -44,7 +44,7 @@ export function registerPrompts(server) {
|
|
|
44
44
|
"",
|
|
45
45
|
"Steps:",
|
|
46
46
|
"1. Use search_agents to find relevant agents",
|
|
47
|
-
"2. Use
|
|
47
|
+
"2. Use get_agent to inspect the top candidate (schema, pricing, rating)",
|
|
48
48
|
"3. Recommend the best one based on price, rating, and success rate",
|
|
49
49
|
"4. Ask if I want to run it",
|
|
50
50
|
].join("\n"),
|
package/dist/tools/agent-info.js
CHANGED
|
@@ -14,14 +14,26 @@ export function registerAgentInfoTools(server) {
|
|
|
14
14
|
const payment = (a.payment ?? {});
|
|
15
15
|
const _pricing = (payment.pricing ?? {});
|
|
16
16
|
const creditPacks = payment.credit_packs;
|
|
17
|
+
const totalJobs = (s.completedJobs ?? a.totalExecutions ?? 0);
|
|
18
|
+
const acceptedPayments = payment.accepted_payments ?? [];
|
|
19
|
+
const paymentLabelMap = {
|
|
20
|
+
tempo_usdc: "tempo",
|
|
21
|
+
base_usdc: "base",
|
|
22
|
+
solana_usdc: "solana",
|
|
23
|
+
stripe_card: "card",
|
|
24
|
+
};
|
|
25
|
+
const paymentLabels = acceptedPayments.map((m) => paymentLabelMap[m] ?? m);
|
|
17
26
|
const lines = [
|
|
18
27
|
`${a.name}`,
|
|
19
|
-
`${stars(a.avgRating ?? s.avgRating)} (${s.ratingCount ?? 0} reviews) • ${compactNumber(
|
|
28
|
+
`${stars(a.avgRating ?? s.avgRating)} (${s.ratingCount ?? 0} reviews) • ${compactNumber(totalJobs)} jobs`,
|
|
20
29
|
"",
|
|
21
30
|
a.description ?? "",
|
|
22
31
|
"",
|
|
23
32
|
`Pricing: ${formatPrice(a.pricePerRunUsd)}`,
|
|
24
|
-
|
|
33
|
+
...(paymentLabels.length > 0 ? [`Accepted payments: ${paymentLabels.join(", ")}`] : []),
|
|
34
|
+
...(totalJobs > 0 && a.successRate != null
|
|
35
|
+
? [`Reliability: ${(Number(a.successRate) * 100).toFixed(0)}% (${compactNumber(totalJobs)} runs)`]
|
|
36
|
+
: []),
|
|
25
37
|
`Avg latency: ${a.avgResponseTimeMs != null ? a.avgResponseTimeMs + "ms" : "N/A"}`,
|
|
26
38
|
...(() => {
|
|
27
39
|
const lastActive = formatLastActive(a.lastActiveAt);
|
|
@@ -82,30 +94,4 @@ export function registerAgentInfoTools(server) {
|
|
|
82
94
|
lines.push("", `ID: ${a.id}`, `View: ${agentWebUrl(a.id)}`);
|
|
83
95
|
return text(lines.join("\n"));
|
|
84
96
|
});
|
|
85
|
-
// ── compare_agents ──────────────────────────────────────────────
|
|
86
|
-
server.tool("compare_agents", "Compare multiple agents side-by-side on rating, price, success rate, and job count.", {
|
|
87
|
-
agent_ids: z
|
|
88
|
-
.array(z.string())
|
|
89
|
-
.min(2)
|
|
90
|
-
.max(5)
|
|
91
|
-
.describe("Agent IDs to compare (2-5)"),
|
|
92
|
-
}, async ({ agent_ids }) => {
|
|
93
|
-
const agents = await Promise.all(agent_ids.map((id) => apiGet(`/agents/${id}`)));
|
|
94
|
-
const header = "Agent Comparison:\n";
|
|
95
|
-
const lines = agents.map((a) => {
|
|
96
|
-
const s = (a.stats ?? {});
|
|
97
|
-
const rating = a.avgRating ?? s.avgRating;
|
|
98
|
-
const jobs = (s.completedJobs ?? a.totalExecutions ?? 0);
|
|
99
|
-
const tipCount = (s.tipCount ?? 0);
|
|
100
|
-
return [
|
|
101
|
-
` ${a.name}`,
|
|
102
|
-
` ${stars(rating)} (${s.ratingCount ?? 0} reviews)${tipCount > 0 ? ` • ${tipCount} tips` : ""}`,
|
|
103
|
-
` ${compactNumber(jobs)} jobs • ${formatPrice(a.pricePerRunUsd)}`,
|
|
104
|
-
` Success: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
|
|
105
|
-
` ${agentWebUrl(a.id)}`,
|
|
106
|
-
"",
|
|
107
|
-
].join("\n");
|
|
108
|
-
});
|
|
109
|
-
return text(header + lines.join("\n"));
|
|
110
|
-
});
|
|
111
97
|
}
|
package/dist/tools/jobs.js
CHANGED
|
@@ -6,39 +6,93 @@ import { formatRunResult } from "../core/formatters.js";
|
|
|
6
6
|
function text(t) {
|
|
7
7
|
return { content: [{ type: "text", text: t }] };
|
|
8
8
|
}
|
|
9
|
+
async function getConsumerWalletAddresses() {
|
|
10
|
+
const addresses = new Set();
|
|
11
|
+
for (const chain of ["tempo", "base", "solana"]) {
|
|
12
|
+
const addr = await getWalletAddress(chain);
|
|
13
|
+
if (addr)
|
|
14
|
+
addresses.add(addr);
|
|
15
|
+
}
|
|
16
|
+
return [...addresses];
|
|
17
|
+
}
|
|
18
|
+
function formatRelativeTime(iso) {
|
|
19
|
+
if (!iso)
|
|
20
|
+
return "";
|
|
21
|
+
const t = new Date(iso).getTime();
|
|
22
|
+
if (!Number.isFinite(t))
|
|
23
|
+
return "";
|
|
24
|
+
const diffSec = Math.max(0, Math.floor((Date.now() - t) / 1000));
|
|
25
|
+
if (diffSec < 60)
|
|
26
|
+
return `${diffSec}s ago`;
|
|
27
|
+
if (diffSec < 3600)
|
|
28
|
+
return `${Math.floor(diffSec / 60)}m ago`;
|
|
29
|
+
if (diffSec < 86400)
|
|
30
|
+
return `${Math.floor(diffSec / 3600)}h ago`;
|
|
31
|
+
return `${Math.floor(diffSec / 86400)}d ago`;
|
|
32
|
+
}
|
|
9
33
|
export function registerJobTools(server) {
|
|
10
34
|
// ── get_job ─────────────────────────────────────────────────────
|
|
11
35
|
server.tool("get_job", "Get the status and output of a job by ID. Use to poll async jobs until they complete.", {
|
|
12
36
|
job_id: z.string().describe("Job ID (UUID)"),
|
|
13
37
|
}, async ({ job_id }) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
38
|
+
const walletLookup = !isAuthenticated() && hasWalletConfigured();
|
|
39
|
+
const addresses = walletLookup ? await getConsumerWalletAddresses() : [""];
|
|
40
|
+
let result = null;
|
|
41
|
+
for (const addr of addresses) {
|
|
42
|
+
const url = addr ? `/jobs/${job_id}?wallet=${encodeURIComponent(addr)}` : `/jobs/${job_id}`;
|
|
43
|
+
try {
|
|
44
|
+
result = await apiGet(url);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const status = err.status;
|
|
49
|
+
if (status !== 404)
|
|
50
|
+
throw err;
|
|
19
51
|
}
|
|
20
52
|
}
|
|
21
|
-
|
|
53
|
+
if (!result) {
|
|
54
|
+
return text(`Job ${job_id} not found. Either the job ID is wrong or it was paid with a wallet not configured here.`);
|
|
55
|
+
}
|
|
22
56
|
if (result.status === "processing") {
|
|
23
57
|
return text(`Job ${job_id} is still processing...`);
|
|
24
58
|
}
|
|
25
59
|
return text(formatRunResult(result));
|
|
26
60
|
});
|
|
27
|
-
// ── list_jobs
|
|
28
|
-
server.tool("list_jobs", "List your recent jobs with status, cost, and
|
|
29
|
-
limit: z.coerce.number().optional().default(10).describe("Max results (1-50)"),
|
|
61
|
+
// ── list_jobs ───────────────────────────────────────────────────
|
|
62
|
+
server.tool("list_jobs", "List your recent jobs across all configured payment wallets, with status, cost, agent, method, and age.", {
|
|
63
|
+
limit: z.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results (1-50)"),
|
|
30
64
|
}, async ({ limit }) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
65
|
+
const requestedLimit = limit ?? 10;
|
|
66
|
+
const walletLookup = !isAuthenticated() && hasWalletConfigured();
|
|
67
|
+
const addresses = walletLookup ? await getConsumerWalletAddresses() : [""];
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
const collected = [];
|
|
70
|
+
for (const addr of addresses) {
|
|
71
|
+
const url = addr
|
|
72
|
+
? `/jobs?wallet=${encodeURIComponent(addr)}&limit=${requestedLimit}`
|
|
73
|
+
: `/jobs?limit=${requestedLimit}`;
|
|
74
|
+
try {
|
|
75
|
+
const jobs = await apiGet(url);
|
|
76
|
+
for (const j of jobs) {
|
|
77
|
+
const id = j.job_id;
|
|
78
|
+
if (!seen.has(id)) {
|
|
79
|
+
seen.add(id);
|
|
80
|
+
collected.push(j);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Skip address that errored; continue with others.
|
|
37
86
|
}
|
|
38
87
|
}
|
|
39
|
-
|
|
40
|
-
if (jobs.length === 0)
|
|
88
|
+
if (collected.length === 0)
|
|
41
89
|
return text("No jobs found.");
|
|
90
|
+
collected.sort((a, b) => {
|
|
91
|
+
const aTime = new Date(a.created_at ?? 0).getTime();
|
|
92
|
+
const bTime = new Date(b.created_at ?? 0).getTime();
|
|
93
|
+
return bTime - aTime;
|
|
94
|
+
});
|
|
95
|
+
const jobs = collected.slice(0, requestedLimit);
|
|
42
96
|
const lines = [`Recent jobs (${jobs.length}):`];
|
|
43
97
|
for (const j of jobs) {
|
|
44
98
|
const status = j.status === "completed"
|
|
@@ -46,10 +100,15 @@ export function registerJobTools(server) {
|
|
|
46
100
|
: j.status === "processing"
|
|
47
101
|
? "\u2026"
|
|
48
102
|
: "\u2717";
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
103
|
+
const amount = (j.settled_amount ?? j.estimated_cost);
|
|
104
|
+
const cost = amount != null ? `$${Number(amount).toFixed(4)}` : "";
|
|
105
|
+
const method = j.settlement_trace
|
|
106
|
+
?.payment_attempt?.payment_method;
|
|
107
|
+
const methodLabel = method ? `[${method.replace(/_usdc$/, "").replace("stripe_", "")}]` : "";
|
|
108
|
+
const shortId = j.job_id?.slice(0, 8);
|
|
109
|
+
const shortAgent = j.agent_id ? String(j.agent_id).slice(0, 8) : "?";
|
|
110
|
+
const when = formatRelativeTime(j.created_at);
|
|
111
|
+
lines.push(` ${status} ${shortId} agent=${shortAgent} ${cost} ${methodLabel} ${when}`.trimEnd());
|
|
53
112
|
}
|
|
54
113
|
return text(lines.join("\n"));
|
|
55
114
|
});
|
package/dist/tools/passes.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { apiGet, apiPostWithPayment } from "../core/api-client.js";
|
|
3
3
|
import { formatCreditPack, formatCreditPackOffer, getCreditPackInventory, getCreditPackProgram, } from "../core/passes.js";
|
|
4
|
-
import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, normalizePaymentMethod, } from "../core/payments.js";
|
|
4
|
+
import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, normalizePaymentMethod, isCardPaymentEnabled, } from "../core/payments.js";
|
|
5
5
|
import { requiresSpendConfirmation } from "../core/config.js";
|
|
6
6
|
import { ensureConsumerPrincipalForMethod } from "../core/principal.js";
|
|
7
7
|
import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
|
|
@@ -39,15 +39,26 @@ export function registerPassTools(server) {
|
|
|
39
39
|
confirmed: z.boolean().optional().describe("Set to true to confirm the purchase after seeing the quote."),
|
|
40
40
|
}, async ({ agent_id, pack_id, pay_with, confirmed }) => {
|
|
41
41
|
if (!hasWalletConfigured()) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
if (isCardPaymentEnabled()) {
|
|
43
|
+
try {
|
|
44
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
45
|
+
return multiText(...formatCardSetupBlocks(url));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Fall through to the setup message below.
|
|
49
|
+
}
|
|
45
50
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
const setupLines = [
|
|
52
|
+
"No payment method configured.",
|
|
53
|
+
"",
|
|
54
|
+
"Supported rails: Tempo USDC, Base USDC, Solana USDC.",
|
|
55
|
+
"Run wallet_setup({ action: \"create\" }) to create a crypto wallet,",
|
|
56
|
+
"or wallet_setup({ action: \"import\" }) with an existing key.",
|
|
57
|
+
];
|
|
58
|
+
if (isCardPaymentEnabled()) {
|
|
59
|
+
setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
|
|
50
60
|
}
|
|
61
|
+
return text(setupLines.join("\n"));
|
|
51
62
|
}
|
|
52
63
|
const agent = await getAgent(agent_id);
|
|
53
64
|
const agentName = agent.name ?? agent_id;
|
package/dist/tools/run.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
|
|
3
3
|
import { uploadLocalFiles } from "../core/file-upload.js";
|
|
4
4
|
import { formatCreditPackOffer, getActiveCreditPack, getCreditPackInventory, getCreditPackProgram, } from "../core/passes.js";
|
|
5
|
-
import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, getWalletAddress, normalizePaymentMethod, } from "../core/payments.js";
|
|
5
|
+
import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, getWalletAddress, normalizePaymentMethod, isCardPaymentEnabled, } from "../core/payments.js";
|
|
6
6
|
import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
|
|
7
7
|
import { formatRunResult } from "../core/formatters.js";
|
|
8
8
|
import { canSpend, recordSpend, requiresPolicyConfirmation } from "../core/spend-policy.js";
|
|
@@ -65,15 +65,26 @@ export function registerRunTools(server) {
|
|
|
65
65
|
confirmed: z.boolean().optional().describe("Set to true to confirm spending after seeing the price quote."),
|
|
66
66
|
}, async ({ agent_id, input, pay_with, confirmed }) => {
|
|
67
67
|
if (!hasWalletConfigured()) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
if (isCardPaymentEnabled()) {
|
|
69
|
+
try {
|
|
70
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
71
|
+
return multiText(...formatCardSetupBlocks(url));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Fall through to the setup message below.
|
|
75
|
+
}
|
|
71
76
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
const setupLines = [
|
|
78
|
+
"No payment method configured.",
|
|
79
|
+
"",
|
|
80
|
+
"Supported rails: Tempo USDC, Base USDC, Solana USDC.",
|
|
81
|
+
"Run wallet_setup({ action: \"create\" }) to create a crypto wallet,",
|
|
82
|
+
"or wallet_setup({ action: \"import\" }) with an existing key.",
|
|
83
|
+
];
|
|
84
|
+
if (isCardPaymentEnabled()) {
|
|
85
|
+
setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
|
|
76
86
|
}
|
|
87
|
+
return text(setupLines.join("\n"));
|
|
77
88
|
}
|
|
78
89
|
let agent;
|
|
79
90
|
try {
|
package/dist/tools/search.js
CHANGED
|
@@ -16,14 +16,15 @@ export function registerSearchTools(server) {
|
|
|
16
16
|
sort: z.enum(["relevance", "price", "rating", "popularity", "newest"]).optional()
|
|
17
17
|
.describe("Sort results by: relevance (default), price, rating, popularity, or newest"),
|
|
18
18
|
}, async ({ query, tag, limit, max_price, min_rating, sort }) => {
|
|
19
|
-
const requestedLimit = limit ?? 10;
|
|
19
|
+
const requestedLimit = Math.max(1, Math.min(50, limit ?? 10));
|
|
20
20
|
const params = new URLSearchParams();
|
|
21
21
|
if (query)
|
|
22
22
|
params.set("q", query);
|
|
23
23
|
if (tag)
|
|
24
24
|
params.set("tag", tag);
|
|
25
|
-
//
|
|
26
|
-
|
|
25
|
+
// min_rating is filtered client-side on avgRating. Request extra candidates
|
|
26
|
+
// so the post-filter result still has a chance of meeting requestedLimit.
|
|
27
|
+
const apiLimit = min_rating ? Math.min(100, requestedLimit * 3) : requestedLimit;
|
|
27
28
|
params.set("limit", String(apiLimit));
|
|
28
29
|
if (max_price)
|
|
29
30
|
params.set("price_max", String(max_price));
|
|
@@ -45,13 +46,13 @@ export function registerSearchTools(server) {
|
|
|
45
46
|
params.set("accepted_payment_methods", acceptedMethods.join(","));
|
|
46
47
|
}
|
|
47
48
|
let agents = await apiGet(`/agents?${params}`);
|
|
48
|
-
// Client-side min_rating filter
|
|
49
|
+
// Client-side min_rating filter — avgRating is the 1-5 user-facing score.
|
|
50
|
+
// Agents with no ratings yet (avgRating == null) are excluded when a
|
|
51
|
+
// minimum is requested, matching user expectation of "at least N stars".
|
|
49
52
|
if (min_rating) {
|
|
50
53
|
agents = agents.filter((a) => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const stars = rating <= 1 ? rating * 5 : rating;
|
|
54
|
-
return stars >= min_rating;
|
|
54
|
+
const avg = a.avgRating ?? a.stats?.avgRating;
|
|
55
|
+
return typeof avg === "number" && avg >= min_rating;
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
// Trim to requested limit after filtering
|
package/dist/tools/solve.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
|
|
3
3
|
import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
|
|
4
|
-
import { getCompatiblePaymentMethods, hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, getWalletAddress, normalizePaymentMethod, toRegistryPaymentMethod, } from "../core/payments.js";
|
|
4
|
+
import { getCompatiblePaymentMethods, hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, getWalletAddress, normalizePaymentMethod, toRegistryPaymentMethod, isCardPaymentEnabled, } from "../core/payments.js";
|
|
5
5
|
import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
|
|
6
6
|
import { agentList, formatRunResult } from "../core/formatters.js";
|
|
7
7
|
import { canSpend, recordSpend, requiresPolicyConfirmation } from "../core/spend-policy.js";
|
|
@@ -114,15 +114,26 @@ export function registerSolveTools(server) {
|
|
|
114
114
|
.describe("Set to true to confirm spending after seeing the price quote."),
|
|
115
115
|
}, async ({ intent, input, budget, pay_with, confirmed }) => {
|
|
116
116
|
if (!hasWalletConfigured()) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
if (isCardPaymentEnabled()) {
|
|
118
|
+
try {
|
|
119
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
120
|
+
return multiText(...formatCardSetupBlocks(url));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Fall through to the setup message below.
|
|
124
|
+
}
|
|
120
125
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
const setupLines = [
|
|
127
|
+
"No payment method configured.",
|
|
128
|
+
"",
|
|
129
|
+
"Supported rails: Tempo USDC, Base USDC, Solana USDC.",
|
|
130
|
+
"Run wallet_setup({ action: \"create\" }) to create a crypto wallet,",
|
|
131
|
+
"or wallet_setup({ action: \"import\" }) with an existing key.",
|
|
132
|
+
];
|
|
133
|
+
if (isCardPaymentEnabled()) {
|
|
134
|
+
setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
|
|
125
135
|
}
|
|
136
|
+
return text(setupLines.join("\n"));
|
|
126
137
|
}
|
|
127
138
|
const pendingKey = makeSolvePendingKey(intent, input, budget);
|
|
128
139
|
const pending = pendingSolves.get(pendingKey);
|
|
@@ -157,7 +168,8 @@ export function registerSolveTools(server) {
|
|
|
157
168
|
const cost = result.cost;
|
|
158
169
|
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
159
170
|
const tipMsg = result.feedback_token ? await autoTip(jobId, agentId, agentName, result.feedback_token) : "";
|
|
160
|
-
|
|
171
|
+
const header = agentName ? [`Matched agent: ${agentName}`, ""].join("\n") : "";
|
|
172
|
+
return multiText(header + formatRunResult(result), feedbackAsk(jobId, agentId, usedCreditPack ? undefined : cost, tipMsg));
|
|
161
173
|
}
|
|
162
174
|
catch (err) {
|
|
163
175
|
const status = err instanceof Error &&
|
|
@@ -254,8 +266,9 @@ export function registerSolveTools(server) {
|
|
|
254
266
|
catch (err) {
|
|
255
267
|
const apiErr = err;
|
|
256
268
|
if (apiErr?.status === 402) {
|
|
269
|
+
const reason = apiErr.message ? `Payment failed: ${apiErr.message}` : "Payment failed — wallet may not have enough funds or the selected method was rejected.";
|
|
257
270
|
return text([
|
|
258
|
-
|
|
271
|
+
reason,
|
|
259
272
|
"",
|
|
260
273
|
"Check your balance and try again.",
|
|
261
274
|
"Use wallet_status to check your current payment methods.",
|
package/dist/tools/wallet.js
CHANGED
|
@@ -65,11 +65,11 @@ export function registerWalletTools(server) {
|
|
|
65
65
|
}
|
|
66
66
|
return text(lines.join("\n"));
|
|
67
67
|
});
|
|
68
|
-
// ── wallet_setup
|
|
69
|
-
server.tool("wallet_setup", "Set up or manage payment
|
|
68
|
+
// ── wallet_setup ────────────────────────────────────────────────
|
|
69
|
+
server.tool("wallet_setup", "Set up or manage a payment wallet. 'create' makes a new encrypted crypto wallet (OWS); 'import' takes an existing private key. Tempo/Base share one EVM key — a single wallet covers both. Solana uses a separate ed25519 key. For crypto wallet deletion or key rotation, direct users to edit ~/.agentwonderland/config.json or ~/.ows/ manually; never handle key material programmatically.", {
|
|
70
70
|
action: z
|
|
71
71
|
.enum(["create", "import", "add-card", "remove-card"])
|
|
72
|
-
.describe("'
|
|
72
|
+
.describe("'create' a crypto wallet (recommended), 'import' an existing key, 'add-card'/'remove-card' for credit card (card-backed MPP availability depends on Stripe SPT)"),
|
|
73
73
|
name: z
|
|
74
74
|
.string()
|
|
75
75
|
.optional()
|
|
@@ -79,7 +79,7 @@ export function registerWalletTools(server) {
|
|
|
79
79
|
.optional()
|
|
80
80
|
.describe("Private key hex string (required for 'import', ignored for 'create')"),
|
|
81
81
|
chain: z.enum(["tempo", "base", "solana"]).optional()
|
|
82
|
-
.describe("Primary chain (default: tempo).
|
|
82
|
+
.describe("Primary chain (default: tempo). Tempo/Base use a shared EVM wallet; Solana uses a separate OWS wallet."),
|
|
83
83
|
}, async ({ action, name, key, chain }) => {
|
|
84
84
|
// ── Card setup flow ──────────────────────────────────────
|
|
85
85
|
if (action === "add-card") {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -14,7 +14,6 @@ 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";
|
|
18
17
|
|
|
19
18
|
// ── Resources ────────────────────────────────────────────────────
|
|
20
19
|
import { registerAgentResources } from "./resources/agents.js";
|
|
@@ -32,41 +31,42 @@ export async function startMcpServer(): Promise<void> {
|
|
|
32
31
|
},
|
|
33
32
|
{
|
|
34
33
|
instructions: [
|
|
35
|
-
"Agent Wonderland — AI agent marketplace. Discover, run,
|
|
34
|
+
"Agent Wonderland — on-demand AI agent marketplace with autonomous per-call payment. Discover, run, rate.",
|
|
36
35
|
"",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
36
|
+
"PURPOSE:",
|
|
37
|
+
"You are an AI agent calling this MCP to extend your capabilities. Instead of integrating N APIs with N keys,",
|
|
38
|
+
"you pay specialist agents per call in USDC. This works headlessly in automations (n8n, cron) and interactively",
|
|
39
|
+
"(Claude Code, Desktop). Favor one-shot `solve()` for autonomous use; prefer `search_agents()` + `run_agent()`",
|
|
40
|
+
"when the caller wants to pick a specific agent.",
|
|
40
41
|
"",
|
|
41
42
|
"WORKFLOW:",
|
|
42
|
-
"1.
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"4.
|
|
47
|
-
"5. Ask user to rate or tip after a successful run",
|
|
43
|
+
"1. solve(intent, input, budget) — one call: find best agent + pay + run. Use when the task is clear.",
|
|
44
|
+
" OR: search_agents() → get_agent() to inspect schema → run_agent() with required fields.",
|
|
45
|
+
"2. If the agent returns status 'processing', poll with get_job(). Async runs resolve automatically.",
|
|
46
|
+
"3. After a successful run, rate_agent() and optionally tip_agent() if the result was useful.",
|
|
47
|
+
"4. Use list_jobs() to recover state across sessions (it checks every configured wallet).",
|
|
48
48
|
"",
|
|
49
|
-
"PAYMENT:",
|
|
50
|
-
"-
|
|
51
|
-
"-
|
|
52
|
-
"-
|
|
53
|
-
"
|
|
54
|
-
"-
|
|
55
|
-
"-
|
|
56
|
-
"-
|
|
57
|
-
"- Use open_observability_dashboard() to open a secure web usage dashboard for runs/spend/rebates.",
|
|
58
|
-
"- Payment is automatic once configured. Users are never charged for failed runs.",
|
|
59
|
-
"- Do NOT ask the user to set up payment before they try to run an agent. Let them explore freely.",
|
|
60
|
-
"- If a specific payment method fails, report the error clearly. Do NOT silently fall back to a different method.",
|
|
61
|
-
"- When payment fails, suggest alternatives using wallet_status.",
|
|
49
|
+
"PAYMENT (crypto-only right now):",
|
|
50
|
+
"- Supported rails: Tempo USDC, Base USDC, Solana USDC. Card is temporarily disabled pending Stripe SPT approval.",
|
|
51
|
+
"- Tempo and Base share one EVM wallet key. Solana uses a separate ed25519 key. One OWS wallet can manage both.",
|
|
52
|
+
"- If pay_with is omitted, the MCP auto-selects a compatible configured rail. Pass pay_with explicitly",
|
|
53
|
+
" (tempo | base | solana | wallet-id) for deterministic behavior.",
|
|
54
|
+
"- Payment is automatic: on a 402 challenge the MCP signs on-chain, submits, then retries. Failed runs are refunded.",
|
|
55
|
+
"- If a specific rail fails, surface the real reason — do NOT silently retry with a different method.",
|
|
56
|
+
"- Headless/automation: set wallet_set_policy() to cap max_per_tx and max_per_day so a runaway loop can't drain funds.",
|
|
62
57
|
"",
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
58
|
+
"FILE HANDLING:",
|
|
59
|
+
"Pass local file paths directly as input values (e.g. image: \"/path/to/photo.jpg\"). The MCP auto-uploads to",
|
|
60
|
+
"cloud storage and replaces with a URL before sending to the agent. Never base64-encode.",
|
|
66
61
|
"",
|
|
67
62
|
"REQUIRED FIELDS:",
|
|
68
|
-
"Always
|
|
69
|
-
"
|
|
63
|
+
"Always inspect the agent's input schema via get_agent() before calling run_agent(). Include ALL required",
|
|
64
|
+
"fields in the first attempt — validation errors cost a round trip.",
|
|
65
|
+
"",
|
|
66
|
+
"WALLET HYGIENE:",
|
|
67
|
+
"- wallet_status shows per-chain USDC balance and the active network (mainnet vs testnet).",
|
|
68
|
+
"- To create or import a crypto wallet: wallet_setup({ action: \"create\" }) or { action: \"import\", key }.",
|
|
69
|
+
"- NEVER delete or rotate keys programmatically. Direct users to edit ~/.agentwonderland/config.json or ~/.ows/ manually.",
|
|
70
70
|
].join("\n"),
|
|
71
71
|
},
|
|
72
72
|
);
|
|
@@ -82,7 +82,6 @@ export async function startMcpServer(): Promise<void> {
|
|
|
82
82
|
registerFavoriteTools(server);
|
|
83
83
|
registerTipTools(server);
|
|
84
84
|
registerPassTools(server);
|
|
85
|
-
registerObservabilityTools(server);
|
|
86
85
|
|
|
87
86
|
// Register resources
|
|
88
87
|
registerAgentResources(server);
|
package/src/prompts/index.ts
CHANGED
|
@@ -62,7 +62,7 @@ export function registerPrompts(server: McpServer) {
|
|
|
62
62
|
"",
|
|
63
63
|
"Steps:",
|
|
64
64
|
"1. Use search_agents to find relevant agents",
|
|
65
|
-
"2. Use
|
|
65
|
+
"2. Use get_agent to inspect the top candidate (schema, pricing, rating)",
|
|
66
66
|
"3. Recommend the best one based on price, rating, and success rate",
|
|
67
67
|
"4. Ask if I want to run it",
|
|
68
68
|
].join("\n"),
|
package/src/tools/agent-info.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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,16 +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
|
-
|
|
41
|
+
const walletLookup = !isAuthenticated() && hasWalletConfigured();
|
|
42
|
+
const addresses = walletLookup ? await getConsumerWalletAddresses() : [""];
|
|
22
43
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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;
|
|
27
53
|
}
|
|
28
54
|
}
|
|
29
55
|
|
|
30
|
-
|
|
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
|
+
|
|
31
60
|
if (result.status === "processing") {
|
|
32
61
|
return text(`Job ${job_id} is still processing...`);
|
|
33
62
|
}
|
|
@@ -35,26 +64,46 @@ export function registerJobTools(server: McpServer): void {
|
|
|
35
64
|
},
|
|
36
65
|
);
|
|
37
66
|
|
|
38
|
-
// ── list_jobs
|
|
67
|
+
// ── list_jobs ───────────────────────────────────────────────────
|
|
39
68
|
server.tool(
|
|
40
69
|
"list_jobs",
|
|
41
|
-
"List your recent jobs with status, cost, and
|
|
70
|
+
"List your recent jobs across all configured payment wallets, with status, cost, agent, method, and age.",
|
|
42
71
|
{
|
|
43
|
-
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)"),
|
|
44
73
|
},
|
|
45
74
|
async ({ limit }) => {
|
|
46
|
-
|
|
75
|
+
const requestedLimit = limit ?? 10;
|
|
76
|
+
const walletLookup = !isAuthenticated() && hasWalletConfigured();
|
|
77
|
+
const addresses = walletLookup ? await getConsumerWalletAddresses() : [""];
|
|
47
78
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
53
96
|
}
|
|
54
97
|
}
|
|
55
98
|
|
|
56
|
-
|
|
57
|
-
|
|
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);
|
|
58
107
|
|
|
59
108
|
const lines = [`Recent jobs (${jobs.length}):`];
|
|
60
109
|
for (const j of jobs) {
|
|
@@ -64,12 +113,16 @@ export function registerJobTools(server: McpServer): void {
|
|
|
64
113
|
: j.status === "processing"
|
|
65
114
|
? "\u2026"
|
|
66
115
|
: "\u2717";
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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);
|
|
71
124
|
lines.push(
|
|
72
|
-
` ${status} ${
|
|
125
|
+
` ${status} ${shortId} agent=${shortAgent} ${cost} ${methodLabel} ${when}`.trimEnd(),
|
|
73
126
|
);
|
|
74
127
|
}
|
|
75
128
|
return text(lines.join("\n"));
|
package/src/tools/passes.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getConfiguredMethods,
|
|
13
13
|
hasWalletConfigured,
|
|
14
14
|
normalizePaymentMethod,
|
|
15
|
+
isCardPaymentEnabled,
|
|
15
16
|
} from "../core/payments.js";
|
|
16
17
|
import { requiresSpendConfirmation } from "../core/config.js";
|
|
17
18
|
import { ensureConsumerPrincipalForMethod } from "../core/principal.js";
|
|
@@ -71,16 +72,25 @@ export function registerPassTools(server: McpServer): void {
|
|
|
71
72
|
},
|
|
72
73
|
async ({ agent_id, pack_id, pay_with, confirmed }) => {
|
|
73
74
|
if (!hasWalletConfigured()) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
}
|
|
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.");
|
|
83
92
|
}
|
|
93
|
+
return text(setupLines.join("\n"));
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
const agent = await getAgent(agent_id);
|
package/src/tools/run.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
hasWalletConfigured,
|
|
15
15
|
getWalletAddress,
|
|
16
16
|
normalizePaymentMethod,
|
|
17
|
+
isCardPaymentEnabled,
|
|
17
18
|
} from "../core/payments.js";
|
|
18
19
|
import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
|
|
19
20
|
import { formatRunResult } from "../core/formatters.js";
|
|
@@ -106,16 +107,25 @@ export function registerRunTools(server: McpServer): void {
|
|
|
106
107
|
},
|
|
107
108
|
async ({ agent_id, input, pay_with, confirmed }) => {
|
|
108
109
|
if (!hasWalletConfigured()) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
if (isCardPaymentEnabled()) {
|
|
111
|
+
try {
|
|
112
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
113
|
+
return multiText(...formatCardSetupBlocks(url));
|
|
114
|
+
} catch {
|
|
115
|
+
// Fall through to the setup message below.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const setupLines = [
|
|
119
|
+
"No payment method configured.",
|
|
120
|
+
"",
|
|
121
|
+
"Supported rails: Tempo USDC, Base USDC, Solana USDC.",
|
|
122
|
+
"Run wallet_setup({ action: \"create\" }) to create a crypto wallet,",
|
|
123
|
+
"or wallet_setup({ action: \"import\" }) with an existing key.",
|
|
124
|
+
];
|
|
125
|
+
if (isCardPaymentEnabled()) {
|
|
126
|
+
setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
|
|
118
127
|
}
|
|
128
|
+
return text(setupLines.join("\n"));
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
let agent: AgentRecord;
|
package/src/tools/search.ts
CHANGED
|
@@ -24,13 +24,14 @@ export function registerSearchTools(server: McpServer): void {
|
|
|
24
24
|
.describe("Sort results by: relevance (default), price, rating, popularity, or newest"),
|
|
25
25
|
},
|
|
26
26
|
async ({ query, tag, limit, max_price, min_rating, sort }) => {
|
|
27
|
-
const requestedLimit = limit ?? 10;
|
|
27
|
+
const requestedLimit = Math.max(1, Math.min(50, limit ?? 10));
|
|
28
28
|
const params = new URLSearchParams();
|
|
29
29
|
if (query) params.set("q", query);
|
|
30
30
|
if (tag) params.set("tag", tag);
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
|
|
32
|
+
// min_rating is filtered client-side on avgRating. Request extra candidates
|
|
33
|
+
// so the post-filter result still has a chance of meeting requestedLimit.
|
|
34
|
+
const apiLimit = min_rating ? Math.min(100, requestedLimit * 3) : requestedLimit;
|
|
34
35
|
params.set("limit", String(apiLimit));
|
|
35
36
|
|
|
36
37
|
if (max_price) params.set("price_max", String(max_price));
|
|
@@ -56,13 +57,13 @@ export function registerSearchTools(server: McpServer): void {
|
|
|
56
57
|
|
|
57
58
|
let agents = await apiGet<AgentRecord[]>(`/agents?${params}`);
|
|
58
59
|
|
|
59
|
-
// Client-side min_rating filter
|
|
60
|
+
// Client-side min_rating filter — avgRating is the 1-5 user-facing score.
|
|
61
|
+
// Agents with no ratings yet (avgRating == null) are excluded when a
|
|
62
|
+
// minimum is requested, matching user expectation of "at least N stars".
|
|
60
63
|
if (min_rating) {
|
|
61
|
-
agents = agents.filter((a
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
const stars = rating <= 1 ? rating * 5 : rating;
|
|
65
|
-
return stars >= min_rating;
|
|
64
|
+
agents = agents.filter((a) => {
|
|
65
|
+
const avg = a.avgRating ?? (a.stats as { avgRating?: number | null } | undefined)?.avgRating;
|
|
66
|
+
return typeof avg === "number" && avg >= min_rating;
|
|
66
67
|
});
|
|
67
68
|
}
|
|
68
69
|
|
package/src/tools/solve.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getWalletAddress,
|
|
11
11
|
normalizePaymentMethod,
|
|
12
12
|
toRegistryPaymentMethod,
|
|
13
|
+
isCardPaymentEnabled,
|
|
13
14
|
} from "../core/payments.js";
|
|
14
15
|
import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
|
|
15
16
|
import { agentList, formatRunResult } from "../core/formatters.js";
|
|
@@ -149,16 +150,25 @@ export function registerSolveTools(server: McpServer): void {
|
|
|
149
150
|
},
|
|
150
151
|
async ({ intent, input, budget, pay_with, confirmed }) => {
|
|
151
152
|
if (!hasWalletConfigured()) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
153
|
+
if (isCardPaymentEnabled()) {
|
|
154
|
+
try {
|
|
155
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
156
|
+
return multiText(...formatCardSetupBlocks(url));
|
|
157
|
+
} catch {
|
|
158
|
+
// Fall through to the setup message below.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const setupLines = [
|
|
162
|
+
"No payment method configured.",
|
|
163
|
+
"",
|
|
164
|
+
"Supported rails: Tempo USDC, Base USDC, Solana USDC.",
|
|
165
|
+
"Run wallet_setup({ action: \"create\" }) to create a crypto wallet,",
|
|
166
|
+
"or wallet_setup({ action: \"import\" }) with an existing key.",
|
|
167
|
+
];
|
|
168
|
+
if (isCardPaymentEnabled()) {
|
|
169
|
+
setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
|
|
161
170
|
}
|
|
171
|
+
return text(setupLines.join("\n"));
|
|
162
172
|
}
|
|
163
173
|
|
|
164
174
|
const pendingKey = makeSolvePendingKey(intent, input, budget);
|
|
@@ -200,7 +210,8 @@ export function registerSolveTools(server: McpServer): void {
|
|
|
200
210
|
const cost = (result as Record<string, unknown>).cost as number | undefined;
|
|
201
211
|
const usedCreditPack = (result as Record<string, unknown>).consumption_mode === "credit_pack";
|
|
202
212
|
const tipMsg = result.feedback_token ? await autoTip(jobId, agentId, agentName, result.feedback_token as string) : "";
|
|
203
|
-
|
|
213
|
+
const header = agentName ? [`Matched agent: ${agentName}`, ""].join("\n") : "";
|
|
214
|
+
return multiText(header + formatRunResult(result), feedbackAsk(jobId, agentId, usedCreditPack ? undefined : cost, tipMsg));
|
|
204
215
|
} catch (err: unknown) {
|
|
205
216
|
const status =
|
|
206
217
|
err instanceof Error &&
|
|
@@ -318,9 +329,10 @@ export function registerSolveTools(server: McpServer): void {
|
|
|
318
329
|
} catch (err: unknown) {
|
|
319
330
|
const apiErr = err as { status?: number; message?: string };
|
|
320
331
|
if (apiErr?.status === 402) {
|
|
332
|
+
const reason = apiErr.message ? `Payment failed: ${apiErr.message}` : "Payment failed — wallet may not have enough funds or the selected method was rejected.";
|
|
321
333
|
return text(
|
|
322
334
|
[
|
|
323
|
-
|
|
335
|
+
reason,
|
|
324
336
|
"",
|
|
325
337
|
"Check your balance and try again.",
|
|
326
338
|
"Use wallet_status to check your current payment methods.",
|
package/src/tools/wallet.ts
CHANGED
|
@@ -101,14 +101,14 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
101
101
|
},
|
|
102
102
|
);
|
|
103
103
|
|
|
104
|
-
// ── wallet_setup
|
|
104
|
+
// ── wallet_setup ────────────────────────────────────────────────
|
|
105
105
|
server.tool(
|
|
106
106
|
"wallet_setup",
|
|
107
|
-
"Set up or manage payment
|
|
107
|
+
"Set up or manage a payment wallet. 'create' makes a new encrypted crypto wallet (OWS); 'import' takes an existing private key. Tempo/Base share one EVM key — a single wallet covers both. Solana uses a separate ed25519 key. For crypto wallet deletion or key rotation, direct users to edit ~/.agentwonderland/config.json or ~/.ows/ manually; never handle key material programmatically.",
|
|
108
108
|
{
|
|
109
109
|
action: z
|
|
110
110
|
.enum(["create", "import", "add-card", "remove-card"])
|
|
111
|
-
.describe("'
|
|
111
|
+
.describe("'create' a crypto wallet (recommended), 'import' an existing key, 'add-card'/'remove-card' for credit card (card-backed MPP availability depends on Stripe SPT)"),
|
|
112
112
|
name: z
|
|
113
113
|
.string()
|
|
114
114
|
.optional()
|
|
@@ -120,7 +120,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
120
120
|
"Private key hex string (required for 'import', ignored for 'create')",
|
|
121
121
|
),
|
|
122
122
|
chain: z.enum(["tempo", "base", "solana"]).optional()
|
|
123
|
-
.describe("Primary chain (default: tempo).
|
|
123
|
+
.describe("Primary chain (default: tempo). Tempo/Base use a shared EVM wallet; Solana uses a separate OWS wallet."),
|
|
124
124
|
},
|
|
125
125
|
async ({ action, name, key, chain }) => {
|
|
126
126
|
// ── Card setup flow ──────────────────────────────────────
|
|
@@ -1,43 +0,0 @@
|
|
|
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
|
-
}
|