@agentwonderland/mcp 0.1.1

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 (71) hide show
  1. package/dist/core/api-client.d.ts +14 -0
  2. package/dist/core/api-client.js +98 -0
  3. package/dist/core/config.d.ts +77 -0
  4. package/dist/core/config.js +297 -0
  5. package/dist/core/formatters.d.ts +70 -0
  6. package/dist/core/formatters.js +193 -0
  7. package/dist/core/index.d.ts +6 -0
  8. package/dist/core/index.js +6 -0
  9. package/dist/core/ows-adapter.d.ts +43 -0
  10. package/dist/core/ows-adapter.js +100 -0
  11. package/dist/core/payments.d.ts +41 -0
  12. package/dist/core/payments.js +254 -0
  13. package/dist/core/types.d.ts +27 -0
  14. package/dist/core/types.js +4 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +53 -0
  17. package/dist/prompts/index.d.ts +2 -0
  18. package/dist/prompts/index.js +89 -0
  19. package/dist/resources/agents.d.ts +2 -0
  20. package/dist/resources/agents.js +34 -0
  21. package/dist/resources/jobs.d.ts +2 -0
  22. package/dist/resources/jobs.js +15 -0
  23. package/dist/resources/wallet.d.ts +2 -0
  24. package/dist/resources/wallet.js +26 -0
  25. package/dist/tools/_token-cache.d.ts +5 -0
  26. package/dist/tools/_token-cache.js +9 -0
  27. package/dist/tools/agent-info.d.ts +2 -0
  28. package/dist/tools/agent-info.js +97 -0
  29. package/dist/tools/favorites.d.ts +2 -0
  30. package/dist/tools/favorites.js +51 -0
  31. package/dist/tools/index.d.ts +9 -0
  32. package/dist/tools/index.js +9 -0
  33. package/dist/tools/jobs.d.ts +2 -0
  34. package/dist/tools/jobs.js +49 -0
  35. package/dist/tools/rate.d.ts +2 -0
  36. package/dist/tools/rate.js +44 -0
  37. package/dist/tools/run.d.ts +2 -0
  38. package/dist/tools/run.js +80 -0
  39. package/dist/tools/search.d.ts +2 -0
  40. package/dist/tools/search.js +81 -0
  41. package/dist/tools/solve.d.ts +2 -0
  42. package/dist/tools/solve.js +124 -0
  43. package/dist/tools/tip.d.ts +2 -0
  44. package/dist/tools/tip.js +40 -0
  45. package/dist/tools/wallet.d.ts +2 -0
  46. package/dist/tools/wallet.js +197 -0
  47. package/package.json +49 -0
  48. package/src/core/api-client.ts +114 -0
  49. package/src/core/config.ts +384 -0
  50. package/src/core/formatters.ts +256 -0
  51. package/src/core/index.ts +6 -0
  52. package/src/core/ows-adapter.ts +214 -0
  53. package/src/core/payments.ts +278 -0
  54. package/src/core/types.ts +28 -0
  55. package/src/index.ts +65 -0
  56. package/src/prompts/index.ts +120 -0
  57. package/src/resources/agents.ts +37 -0
  58. package/src/resources/jobs.ts +17 -0
  59. package/src/resources/wallet.ts +30 -0
  60. package/src/tools/_token-cache.ts +18 -0
  61. package/src/tools/agent-info.ts +120 -0
  62. package/src/tools/favorites.ts +74 -0
  63. package/src/tools/index.ts +9 -0
  64. package/src/tools/jobs.ts +69 -0
  65. package/src/tools/rate.ts +62 -0
  66. package/src/tools/run.ts +97 -0
  67. package/src/tools/search.ts +96 -0
  68. package/src/tools/solve.ts +162 -0
  69. package/src/tools/tip.ts +59 -0
  70. package/src/tools/wallet.ts +268 -0
  71. package/tsconfig.json +15 -0
@@ -0,0 +1,37 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { apiGet } from "../core/api-client.js";
3
+ import { formatPrice, stars, compactNumber } from "../core/formatters.js";
4
+
5
+ export function registerAgentResources(server: McpServer) {
6
+ // Agent directory — browsable list of all agents
7
+ server.resource("agent-directory", "aw://agents", async () => {
8
+ const agents = await apiGet<any[]>("/agents?limit=50");
9
+ const lines = agents.map(a => {
10
+ const rating = stars(a.reputationScore);
11
+ const jobs = compactNumber(a.totalExecutions);
12
+ const price = formatPrice(a.pricePer1kTokens);
13
+ return `${a.name} ${rating} ${jobs} jobs | ${price}/req — ${a.description || ""}`;
14
+ });
15
+ return {
16
+ contents: [{
17
+ uri: "aw://agents",
18
+ mimeType: "text/plain",
19
+ text: `Agent Wonderland Marketplace (${agents.length} agents)\n\n${lines.join("\n")}`,
20
+ }],
21
+ };
22
+ });
23
+
24
+ // Agent schema — input schema for a specific agent
25
+ server.resource("agent-schema", "aw://agents/{id}/schema", async (uri) => {
26
+ const id = uri.pathname?.split("/").pop() || uri.href.split("/").pop()?.split("?")[0];
27
+ const agent = await apiGet<any>(`/agents/${id}`);
28
+ const schema = agent.mcpSchema ? JSON.stringify(agent.mcpSchema, null, 2) : "No schema defined";
29
+ return {
30
+ contents: [{
31
+ uri: uri.href,
32
+ mimeType: "application/json",
33
+ text: schema,
34
+ }],
35
+ };
36
+ });
37
+ }
@@ -0,0 +1,17 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { apiGet } from "../core/api-client.js";
3
+ import { formatRunResult } from "../core/formatters.js";
4
+
5
+ export function registerJobResources(server: McpServer) {
6
+ server.resource("job-result", "aw://jobs/{id}", async (uri) => {
7
+ const id = uri.pathname?.split("/").pop() || uri.href.split("/").pop()?.split("?")[0];
8
+ const job = await apiGet<any>(`/jobs/${id}`);
9
+ return {
10
+ contents: [{
11
+ uri: uri.href,
12
+ mimeType: "text/plain",
13
+ text: formatRunResult(job),
14
+ }],
15
+ };
16
+ });
17
+ }
@@ -0,0 +1,30 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { getWallets, getCardConfig } from "../core/config.js";
3
+ import { getWalletAddress, getConfiguredMethods } from "../core/payments.js";
4
+
5
+ export function registerWalletResources(server: McpServer) {
6
+ server.resource("wallet-config", "aw://wallet", async () => {
7
+ const wallets = getWallets();
8
+ const card = getCardConfig();
9
+ const methods = getConfiguredMethods();
10
+
11
+ const lines = ["Wallet Configuration", ""];
12
+ for (const w of wallets) {
13
+ const addr = await getWalletAddress(w.id);
14
+ const storage = w.keyType === "ows" ? "[encrypted]" : "[plaintext]";
15
+ lines.push(`${w.id} ${storage}: ${w.chains.join(", ")} — ${addr}`);
16
+ }
17
+ if (card) {
18
+ lines.push(`Card: ${card.brand} ****${card.last4}`);
19
+ }
20
+ lines.push("", `Configured methods: ${methods.join(", ") || "none"}`);
21
+
22
+ return {
23
+ contents: [{
24
+ uri: "aw://wallet",
25
+ mimeType: "text/plain",
26
+ text: lines.join("\n"),
27
+ }],
28
+ };
29
+ });
30
+ }
@@ -0,0 +1,18 @@
1
+ // Feedback token cache — stores tokens from recent run/solve results
2
+ // Tokens expire after 1 hour on the server, so no need for client-side cleanup
3
+
4
+ const cache = new Map<string, { token: string; agent_id: string }>();
5
+
6
+ export function storeFeedbackToken(
7
+ jobId: string,
8
+ token: string,
9
+ agentId: string,
10
+ ): void {
11
+ cache.set(jobId, { token, agent_id: agentId });
12
+ }
13
+
14
+ export function getFeedbackToken(
15
+ jobId: string,
16
+ ): { token: string; agent_id: string } | undefined {
17
+ return cache.get(jobId);
18
+ }
@@ -0,0 +1,120 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiGet } from "../core/api-client.js";
4
+ import { formatPrice, stars, compactNumber, outputTypeHint, agentWebUrl, formatFeedbackSummary, formatLastActive } from "../core/formatters.js";
5
+ import type { AgentRecord } from "../core/types.js";
6
+
7
+ function text(t: string) {
8
+ return { content: [{ type: "text" as const, text: t }] };
9
+ }
10
+
11
+ export function registerAgentInfoTools(server: McpServer): void {
12
+ // ── get_agent (renamed from agent_profile) ──────────────────────
13
+ server.tool(
14
+ "get_agent",
15
+ "Get detailed information about a specific agent including description, pricing, success rate, latency, and tags.",
16
+ {
17
+ agent_id: z.string().describe("Agent ID (UUID) or slug"),
18
+ },
19
+ async ({ agent_id }) => {
20
+ const a = await apiGet<AgentRecord>(`/agents/${agent_id}`);
21
+ const s = (a.stats ?? {}) as Record<string, unknown>;
22
+ const payment = (a.payment ?? {}) as Record<string, unknown>;
23
+ const _pricing = (payment.pricing ?? {}) as Record<string, unknown>;
24
+
25
+ const lines = [
26
+ `${a.name}`,
27
+ `${stars(a.avgRating ?? (s.avgRating as number))} (${s.ratingCount ?? 0} reviews) • ${compactNumber((s.completedJobs ?? a.totalExecutions ?? 0) as number)} jobs`,
28
+ "",
29
+ (a.description as string) ?? "",
30
+ "",
31
+ `Pricing: ${formatPrice(a.pricePer1kTokens, a.pricingModel)}`,
32
+ `Reliability: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
33
+ `Avg latency: ${(a.avgResponseTimeMs as number) != null ? a.avgResponseTimeMs + "ms" : "N/A"}`,
34
+ ...(() => {
35
+ const lastActive = formatLastActive(a.lastActiveAt as string | null);
36
+ return lastActive ? [lastActive] : [];
37
+ })(),
38
+ "",
39
+ `Tags: ${(a.tags as string[])?.join(", ") ?? "none"}`,
40
+ ...(() => {
41
+ const hint = outputTypeHint(a.tags as string[]);
42
+ return hint ? [`Output: ${hint}`] : [];
43
+ })(),
44
+ ];
45
+
46
+ // Feedback summary (rating + tips)
47
+ const feedbackSummary = formatFeedbackSummary(s);
48
+ if (feedbackSummary) {
49
+ lines.push("", feedbackSummary);
50
+ }
51
+
52
+ // Fetch recent reviews with comments
53
+ try {
54
+ const reviews = await apiGet<{ reviews: Array<{ rating: number; comment: string | null; createdAt: string }>; total: number }>(`/agents/${a.id}/reviews?limit=3&sort=recent`);
55
+ const withComments = reviews.reviews?.filter((r) => r.comment);
56
+ if (withComments?.length) {
57
+ lines.push("", "Recent reviews:");
58
+ for (const r of withComments) {
59
+ lines.push(` ${stars(r.rating)} "${r.comment}"`);
60
+ }
61
+ }
62
+ } catch {
63
+ // Reviews endpoint may not be available
64
+ }
65
+
66
+ // Input schema — show what fields the agent accepts
67
+ const schema = (a.schema as Record<string, unknown>)?.input as Record<string, unknown> | undefined;
68
+ const inputSchema = (schema?.inputSchema ?? (a.mcpSchema as Record<string, unknown>)?.inputSchema) as Record<string, unknown> | undefined;
69
+ if (inputSchema?.properties) {
70
+ const props = inputSchema.properties as Record<string, { type?: string; description?: string }>;
71
+ const required = new Set((inputSchema.required as string[]) ?? []);
72
+ lines.push("", "Input fields:");
73
+ for (const [name, def] of Object.entries(props)) {
74
+ const req = required.has(name) ? " (required)" : "";
75
+ const desc = def.description ? ` — ${def.description}` : "";
76
+ lines.push(` ${name}: ${def.type ?? "string"}${req}${desc}`);
77
+ }
78
+ }
79
+
80
+ lines.push("", `ID: ${a.id}`, `View: ${agentWebUrl(a.id)}`);
81
+ return text(lines.join("\n"));
82
+ },
83
+ );
84
+
85
+ // ── compare_agents ──────────────────────────────────────────────
86
+ server.tool(
87
+ "compare_agents",
88
+ "Compare multiple agents side-by-side on rating, price, success rate, and job count.",
89
+ {
90
+ agent_ids: z
91
+ .array(z.string())
92
+ .min(2)
93
+ .max(5)
94
+ .describe("Agent IDs to compare (2-5)"),
95
+ },
96
+ async ({ agent_ids }) => {
97
+ const agents = await Promise.all(
98
+ agent_ids.map((id) => apiGet<AgentRecord>(`/agents/${id}`)),
99
+ );
100
+
101
+ const header = "Agent Comparison:\n";
102
+ const lines = agents.map((a) => {
103
+ const s = (a.stats ?? {}) as Record<string, unknown>;
104
+ const rating = a.avgRating ?? (s.avgRating as number);
105
+ const jobs = (s.completedJobs ?? a.totalExecutions ?? 0) as number;
106
+ const tipCount = (s.tipCount ?? 0) as number;
107
+ return [
108
+ ` ${a.name}`,
109
+ ` ${stars(rating)} (${s.ratingCount ?? 0} reviews)${tipCount > 0 ? ` • ${tipCount} tips` : ""}`,
110
+ ` ${compactNumber(jobs)} jobs • ${formatPrice(a.pricePer1kTokens, a.pricingModel)}`,
111
+ ` Success: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
112
+ ` ${agentWebUrl(a.id)}`,
113
+ "",
114
+ ].join("\n");
115
+ });
116
+
117
+ return text(header + lines.join("\n"));
118
+ },
119
+ );
120
+ }
@@ -0,0 +1,74 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { getFavorites, addFavorite, removeFavorite } from "../core/config.js";
4
+ import { apiGet } from "../core/api-client.js";
5
+ import { formatPrice, stars, compactNumber } from "../core/formatters.js";
6
+
7
+ function text(t: string) {
8
+ return { content: [{ type: "text" as const, text: t }] };
9
+ }
10
+
11
+ export function registerFavoriteTools(server: McpServer) {
12
+ // favorite_agent — add an agent to favorites
13
+ server.tool(
14
+ "favorite_agent",
15
+ "Save an agent to your favorites for quick access",
16
+ {
17
+ agent_id: z.string().describe("Agent ID to favorite"),
18
+ },
19
+ async ({ agent_id }) => {
20
+ // Verify agent exists
21
+ const agent = await apiGet<Record<string, unknown>>(`/agents/${agent_id}`);
22
+ addFavorite(agent_id);
23
+ return text(`★ Added ${agent.name} to favorites`);
24
+ },
25
+ );
26
+
27
+ // unfavorite_agent — remove from favorites
28
+ server.tool(
29
+ "unfavorite_agent",
30
+ "Remove an agent from your favorites",
31
+ {
32
+ agent_id: z.string().describe("Agent ID to remove"),
33
+ },
34
+ async ({ agent_id }) => {
35
+ removeFavorite(agent_id);
36
+ return text("Removed from favorites");
37
+ },
38
+ );
39
+
40
+ // list_favorites — show all favorited agents with details
41
+ server.tool(
42
+ "list_favorites",
43
+ "List your favorite agents",
44
+ {},
45
+ async () => {
46
+ const ids = getFavorites();
47
+ if (ids.length === 0) {
48
+ return text("No favorites yet. Use favorite_agent to save agents you like.");
49
+ }
50
+
51
+ const lines = ["Your favorite agents:", ""];
52
+ for (const id of ids) {
53
+ try {
54
+ const agent = await apiGet<Record<string, unknown>>(`/agents/${id}`);
55
+ const rating = stars(agent.avgRating as number | null | undefined);
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
+ );
61
+ lines.push(`${agent.name} ${rating} ${jobs} jobs | ${price}`);
62
+ lines.push(` ID: ${id}`);
63
+ if (agent.description) lines.push(` ${agent.description}`);
64
+ lines.push("");
65
+ } catch {
66
+ lines.push(`${id} (not found — may have been removed)`);
67
+ lines.push("");
68
+ }
69
+ }
70
+
71
+ return text(lines.join("\n"));
72
+ },
73
+ );
74
+ }
@@ -0,0 +1,9 @@
1
+ export { registerSearchTools } from "./search.js";
2
+ export { registerRunTools } from "./run.js";
3
+ export { registerSolveTools } from "./solve.js";
4
+ export { registerAgentInfoTools } from "./agent-info.js";
5
+ export { registerJobTools } from "./jobs.js";
6
+ export { registerRateTools } from "./rate.js";
7
+ export { registerWalletTools } from "./wallet.js";
8
+ export { registerFavoriteTools } from "./favorites.js";
9
+ export { registerTipTools } from "./tip.js";
@@ -0,0 +1,69 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiGet } from "../core/api-client.js";
4
+ import { isAuthenticated } from "../core/config.js";
5
+ import { hasWalletConfigured, getWalletAddress } from "../core/payments.js";
6
+ import { formatRunResult } from "../core/formatters.js";
7
+
8
+ function text(t: string) {
9
+ return { content: [{ type: "text" as const, text: t }] };
10
+ }
11
+
12
+ export function registerJobTools(server: McpServer): void {
13
+ // ── get_job ─────────────────────────────────────────────────────
14
+ server.tool(
15
+ "get_job",
16
+ "Get the status and output of a job by ID. Use to poll async jobs until they complete.",
17
+ {
18
+ job_id: z.string().describe("Job ID (UUID)"),
19
+ },
20
+ async ({ job_id }) => {
21
+ const result = await apiGet<Record<string, unknown>>(`/jobs/${job_id}`);
22
+ if (result.status === "processing") {
23
+ return text(`Job ${job_id} is still processing...`);
24
+ }
25
+ return text(formatRunResult(result));
26
+ },
27
+ );
28
+
29
+ // ── list_jobs (renamed from list_my_jobs) ───────────────────────
30
+ server.tool(
31
+ "list_jobs",
32
+ "List your recent jobs with status, cost, and agent info.",
33
+ {
34
+ limit: z.coerce.number().optional().default(10).describe("Max results (1-50)"),
35
+ },
36
+ async ({ limit }) => {
37
+ let url = `/jobs?limit=${limit ?? 10}`;
38
+
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)}`;
44
+ }
45
+ }
46
+
47
+ const jobs = await apiGet<Array<Record<string, unknown>>>(url);
48
+ if (jobs.length === 0) return text("No jobs found.");
49
+
50
+ const lines = [`Recent jobs (${jobs.length}):`];
51
+ for (const j of jobs) {
52
+ const status =
53
+ j.status === "completed"
54
+ ? "\u2713"
55
+ : j.status === "processing"
56
+ ? "\u2026"
57
+ : "\u2717";
58
+ const cost =
59
+ j.estimated_cost != null
60
+ ? `$${Number(j.estimated_cost).toFixed(4)}`
61
+ : "";
62
+ 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}`,
64
+ );
65
+ }
66
+ return text(lines.join("\n"));
67
+ },
68
+ );
69
+ }
@@ -0,0 +1,62 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiPost } from "../core/api-client.js";
4
+ import { stars } from "../core/formatters.js";
5
+ import { getFeedbackToken } from "./_token-cache.js";
6
+
7
+ function text(t: string) {
8
+ return { content: [{ type: "text" as const, text: t }] };
9
+ }
10
+
11
+ export function registerRateTools(server: McpServer): void {
12
+ server.tool(
13
+ "rate_agent",
14
+ "Rate an agent after running it. Provide the job ID from the run result. Must be used within 1 hour of running the agent.",
15
+ {
16
+ job_id: z.string().describe("Job ID from the run result"),
17
+ rating: z.number().min(1).max(5).describe("Rating 1-5 stars"),
18
+ comment: z.string().optional().describe("Optional feedback comment"),
19
+ },
20
+ async ({ job_id, rating, comment }) => {
21
+ const tokenData = getFeedbackToken(job_id);
22
+ if (!tokenData) {
23
+ return text(
24
+ "No feedback token found for this job. You can only rate agents immediately after running them.",
25
+ );
26
+ }
27
+
28
+ try {
29
+ await apiPost("/feedback", {
30
+ job_id,
31
+ feedback_token: tokenData.token,
32
+ rating,
33
+ thumb: rating >= 3 ? "up" : "down",
34
+ ...(comment ? { comment } : {}),
35
+ });
36
+ const starStr = stars(rating);
37
+ return text(
38
+ `${starStr} Rating submitted for job ${job_id}${comment ? ` \u2014 "${comment}"` : ""}`,
39
+ );
40
+ } catch (err: unknown) {
41
+ const apiErr = err as {
42
+ status?: number;
43
+ body?: { error?: string };
44
+ message?: string;
45
+ };
46
+ if (apiErr?.status === 401) {
47
+ return text(
48
+ "Feedback token expired. Ratings must be submitted within 1 hour of running the agent.",
49
+ );
50
+ }
51
+ if (apiErr?.status === 409) {
52
+ return text("You've already rated this job.");
53
+ }
54
+ if (apiErr?.status === 429) {
55
+ return text("You can only rate this agent once every 30 days.");
56
+ }
57
+ const msg = apiErr?.body?.error || apiErr?.message || "Rating failed";
58
+ return text(`Could not submit rating: ${msg}`);
59
+ }
60
+ },
61
+ );
62
+ }
@@ -0,0 +1,97 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiGet, apiPostWithPayment } from "../core/api-client.js";
4
+ import { getConfiguredMethods, hasWalletConfigured } from "../core/payments.js";
5
+ import { formatRunResult } from "../core/formatters.js";
6
+ import { storeFeedbackToken } from "./_token-cache.js";
7
+
8
+ function text(t: string) {
9
+ return { content: [{ type: "text" as const, text: t }] };
10
+ }
11
+
12
+ export function registerRunTools(server: McpServer): void {
13
+ server.tool(
14
+ "run_agent",
15
+ "Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking.",
16
+ {
17
+ agent_id: z.string().describe("Agent ID (UUID or slug)"),
18
+ input: z.record(z.unknown()).describe("Input payload for the agent"),
19
+ pay_with: z.string().optional().describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
20
+ },
21
+ async ({ agent_id, input, pay_with }) => {
22
+ if (!hasWalletConfigured()) {
23
+ return text(
24
+ "No wallet configured. Set one up first:\n\n" +
25
+ ' wallet_setup({ action: "create", name: "my-wallet" })\n\n' +
26
+ "Then fund it with USDC on Tempo and try again."
27
+ );
28
+ }
29
+
30
+ // Resolve slug to UUID if needed (slugs don't contain hyphens in UUID positions)
31
+ let resolvedId = agent_id;
32
+ const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(agent_id);
33
+ if (!isUuid) {
34
+ try {
35
+ const agent = await apiGet<{ id: string }>(`/agents/${agent_id}`);
36
+ resolvedId = agent.id;
37
+ } catch {
38
+ return text(`Agent "${agent_id}" not found. Use search_agents to find available agents.`);
39
+ }
40
+ }
41
+
42
+ const method = pay_with;
43
+ let result: Record<string, unknown>;
44
+ try {
45
+ result = await apiPostWithPayment<Record<string, unknown>>(
46
+ `/agents/${resolvedId}/run`,
47
+ { input },
48
+ method,
49
+ );
50
+ } catch (err: unknown) {
51
+ const apiErr = err as { status?: number; message?: string };
52
+ if (apiErr?.status === 402) {
53
+ return text(
54
+ "Payment failed — your wallet may not have enough USDC.\n\n" +
55
+ "Check your balance and fund your wallet, then try again.\n" +
56
+ "Use wallet_status to check your current payment methods."
57
+ );
58
+ }
59
+ const msg = apiErr?.message ?? "Failed to run agent";
60
+ if (msg.includes("Missing required field") || msg.includes("validation failed")) {
61
+ return text(`Error: ${msg}\n\nUse get_agent("${agent_id}") to see the required input fields.`);
62
+ }
63
+ return text(`Error: ${msg}`);
64
+ }
65
+ const formatted = formatRunResult(result, {
66
+ paymentMethod: method ?? getConfiguredMethods()[0],
67
+ });
68
+ const jobId = (result.job_id as string) ?? "";
69
+ const agentId = (result.agent_id as string) ?? agent_id;
70
+
71
+ if (result.feedback_token) {
72
+ storeFeedbackToken(jobId, result.feedback_token as string, agentId);
73
+ }
74
+
75
+ const status = result.status as string;
76
+ let prompt: string;
77
+ if (status === "success") {
78
+ prompt = [
79
+ "",
80
+ "---",
81
+ "How was this result? You can:",
82
+ ` • rate_agent with job_id "${jobId}" and a score (1-5) — within 1 hour`,
83
+ " • tip_agent to show appreciation — within 1 hour",
84
+ " • favorite_agent to save this agent for later",
85
+ ].join("\n");
86
+ } else {
87
+ prompt = [
88
+ "",
89
+ "---",
90
+ "The agent execution failed. A refund has been initiated automatically",
91
+ "and will be returned to your wallet.",
92
+ ].join("\n");
93
+ }
94
+ return text(formatted + prompt);
95
+ },
96
+ );
97
+ }
@@ -0,0 +1,96 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiGet } from "../core/api-client.js";
4
+ import { getAcceptedPaymentMethods } from "../core/payments.js";
5
+ import { isFavorite } from "../core/config.js";
6
+ import { agentLine } from "../core/formatters.js";
7
+ import type { AgentRecord } from "../core/types.js";
8
+
9
+ function text(t: string) {
10
+ return { content: [{ type: "text" as const, text: t }] };
11
+ }
12
+
13
+ export function registerSearchTools(server: McpServer): void {
14
+ server.tool(
15
+ "search_agents",
16
+ "Search the Agent Wonderland marketplace for AI agents. Returns a ranked list of matching agents with ratings, pricing, and job counts.",
17
+ {
18
+ query: z.string().optional().describe("Search query (natural language or keywords)"),
19
+ tag: z.string().optional().describe("Filter by tag (e.g. 'code', 'image', 'data')"),
20
+ limit: z.number().optional().default(10).describe("Max results (1-50)"),
21
+ max_price: z.number().optional().describe("Maximum price per request in USD"),
22
+ min_rating: z.number().min(1).max(5).optional().describe("Minimum star rating (1-5)"),
23
+ sort: z.enum(["relevance", "price", "rating", "popularity", "newest"]).optional()
24
+ .describe("Sort results by: relevance (default), price, rating, popularity, or newest"),
25
+ },
26
+ async ({ query, tag, limit, max_price, min_rating, sort }) => {
27
+ const requestedLimit = limit ?? 10;
28
+ const params = new URLSearchParams();
29
+ if (query) params.set("q", query);
30
+ if (tag) params.set("tag", tag);
31
+
32
+ // When filtering by min_rating client-side, request more results
33
+ const apiLimit = min_rating ? requestedLimit * 3 : requestedLimit;
34
+ params.set("limit", String(apiLimit));
35
+
36
+ if (max_price) params.set("price_max", String(max_price));
37
+
38
+ // Translate sort option to API sort/order params
39
+ if (sort) {
40
+ const sortMap: Record<string, string> = {
41
+ relevance: "reputation",
42
+ rating: "reputation",
43
+ popularity: "jobs",
44
+ price: "price",
45
+ newest: "newest",
46
+ };
47
+ params.set("sort", sortMap[sort] ?? "reputation");
48
+ // Price sort defaults to ascending, everything else descending
49
+ params.set("order", sort === "price" ? "asc" : "desc");
50
+ }
51
+
52
+ const acceptedMethods = getAcceptedPaymentMethods();
53
+ if (acceptedMethods.length > 0) {
54
+ params.set("accepted_payment_methods", acceptedMethods.join(","));
55
+ }
56
+
57
+ let agents = await apiGet<AgentRecord[]>(`/agents?${params}`);
58
+
59
+ // Client-side min_rating filter
60
+ if (min_rating) {
61
+ agents = agents.filter((a: any) => {
62
+ const rating = a.reputationScore ?? a.avgRating ?? 0;
63
+ // Convert 0-1 reputation to 1-5 scale if needed
64
+ const stars = rating <= 1 ? rating * 5 : rating;
65
+ return stars >= min_rating;
66
+ });
67
+ }
68
+
69
+ // Trim to requested limit after filtering
70
+ agents = agents.slice(0, requestedLimit);
71
+
72
+ if (agents.length === 0) {
73
+ return text(query ? `No agents found matching "${query}".` : "No agents found.");
74
+ }
75
+
76
+ // Build header with active filters
77
+ const filters: string[] = [];
78
+ if (max_price) filters.push(`max $${max_price}`);
79
+ if (min_rating) filters.push(`min ${min_rating}\u2605`);
80
+ if (sort && sort !== "relevance") filters.push(`sorted by ${sort}`);
81
+ const filterStr = filters.length > 0 ? ` (${filters.join(", ")})` : "";
82
+
83
+ const header = query
84
+ ? `Found ${agents.length} agent${agents.length === 1 ? "" : "s"} matching "${query}"${filterStr}:`
85
+ : `Found ${agents.length} agent${agents.length === 1 ? "" : "s"}${filterStr}:`;
86
+
87
+ const lines = agents.map((a) => {
88
+ const fav = isFavorite(a.id) ? " \u2605" : "";
89
+ return ` ${agentLine(a)}${fav}`;
90
+ });
91
+
92
+ const footer = "\nBrowse all agents: https://agentwonderland.com/agents";
93
+ return text([header, "", ...lines, footer].join("\n"));
94
+ },
95
+ );
96
+ }