@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,81 @@
1
+ import { z } from "zod";
2
+ import { apiGet } from "../core/api-client.js";
3
+ import { getAcceptedPaymentMethods } from "../core/payments.js";
4
+ import { isFavorite } from "../core/config.js";
5
+ import { agentLine } from "../core/formatters.js";
6
+ function text(t) {
7
+ return { content: [{ type: "text", text: t }] };
8
+ }
9
+ export function registerSearchTools(server) {
10
+ server.tool("search_agents", "Search the Agent Wonderland marketplace for AI agents. Returns a ranked list of matching agents with ratings, pricing, and job counts.", {
11
+ query: z.string().optional().describe("Search query (natural language or keywords)"),
12
+ tag: z.string().optional().describe("Filter by tag (e.g. 'code', 'image', 'data')"),
13
+ limit: z.number().optional().default(10).describe("Max results (1-50)"),
14
+ max_price: z.number().optional().describe("Maximum price per request in USD"),
15
+ min_rating: z.number().min(1).max(5).optional().describe("Minimum star rating (1-5)"),
16
+ sort: z.enum(["relevance", "price", "rating", "popularity", "newest"]).optional()
17
+ .describe("Sort results by: relevance (default), price, rating, popularity, or newest"),
18
+ }, async ({ query, tag, limit, max_price, min_rating, sort }) => {
19
+ const requestedLimit = limit ?? 10;
20
+ const params = new URLSearchParams();
21
+ if (query)
22
+ params.set("q", query);
23
+ if (tag)
24
+ params.set("tag", tag);
25
+ // When filtering by min_rating client-side, request more results
26
+ const apiLimit = min_rating ? requestedLimit * 3 : requestedLimit;
27
+ params.set("limit", String(apiLimit));
28
+ if (max_price)
29
+ params.set("price_max", String(max_price));
30
+ // Translate sort option to API sort/order params
31
+ if (sort) {
32
+ const sortMap = {
33
+ relevance: "reputation",
34
+ rating: "reputation",
35
+ popularity: "jobs",
36
+ price: "price",
37
+ newest: "newest",
38
+ };
39
+ params.set("sort", sortMap[sort] ?? "reputation");
40
+ // Price sort defaults to ascending, everything else descending
41
+ params.set("order", sort === "price" ? "asc" : "desc");
42
+ }
43
+ const acceptedMethods = getAcceptedPaymentMethods();
44
+ if (acceptedMethods.length > 0) {
45
+ params.set("accepted_payment_methods", acceptedMethods.join(","));
46
+ }
47
+ let agents = await apiGet(`/agents?${params}`);
48
+ // Client-side min_rating filter
49
+ if (min_rating) {
50
+ agents = agents.filter((a) => {
51
+ const rating = a.reputationScore ?? a.avgRating ?? 0;
52
+ // Convert 0-1 reputation to 1-5 scale if needed
53
+ const stars = rating <= 1 ? rating * 5 : rating;
54
+ return stars >= min_rating;
55
+ });
56
+ }
57
+ // Trim to requested limit after filtering
58
+ agents = agents.slice(0, requestedLimit);
59
+ if (agents.length === 0) {
60
+ return text(query ? `No agents found matching "${query}".` : "No agents found.");
61
+ }
62
+ // Build header with active filters
63
+ const filters = [];
64
+ if (max_price)
65
+ filters.push(`max $${max_price}`);
66
+ if (min_rating)
67
+ filters.push(`min ${min_rating}\u2605`);
68
+ if (sort && sort !== "relevance")
69
+ filters.push(`sorted by ${sort}`);
70
+ const filterStr = filters.length > 0 ? ` (${filters.join(", ")})` : "";
71
+ const header = query
72
+ ? `Found ${agents.length} agent${agents.length === 1 ? "" : "s"} matching "${query}"${filterStr}:`
73
+ : `Found ${agents.length} agent${agents.length === 1 ? "" : "s"}${filterStr}:`;
74
+ const lines = agents.map((a) => {
75
+ const fav = isFavorite(a.id) ? " \u2605" : "";
76
+ return ` ${agentLine(a)}${fav}`;
77
+ });
78
+ const footer = "\nBrowse all agents: https://agentwonderland.com/agents";
79
+ return text([header, "", ...lines, footer].join("\n"));
80
+ });
81
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSolveTools(server: McpServer): void;
@@ -0,0 +1,124 @@
1
+ import { z } from "zod";
2
+ import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
3
+ import { hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, } from "../core/payments.js";
4
+ import { agentList, formatRunResult } from "../core/formatters.js";
5
+ import { storeFeedbackToken } from "./_token-cache.js";
6
+ function text(t) {
7
+ return { content: [{ type: "text", text: t }] };
8
+ }
9
+ function ratingPrompt(jobId) {
10
+ return [
11
+ "",
12
+ "---",
13
+ "How was this result? You can:",
14
+ ` • rate_agent with job_id "${jobId}" and a score (1-5) — within 1 hour`,
15
+ " • tip_agent to show appreciation — within 1 hour",
16
+ " • favorite_agent to save this agent for later",
17
+ ].join("\n");
18
+ }
19
+ export function registerSolveTools(server) {
20
+ server.tool("solve", "Solve a task by finding the best agent, paying, and executing. The primary way to use the marketplace — describe what you need and the system handles discovery, selection, payment, and execution.", {
21
+ intent: z
22
+ .string()
23
+ .describe("What you want to accomplish (natural language)"),
24
+ input: z
25
+ .record(z.unknown())
26
+ .optional()
27
+ .default({})
28
+ .describe("Input payload for the agent"),
29
+ budget: z
30
+ .number()
31
+ .positive()
32
+ .max(100)
33
+ .optional()
34
+ .default(1.0)
35
+ .describe("Maximum budget in USD"),
36
+ pay_with: z
37
+ .string()
38
+ .optional()
39
+ .describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
40
+ }, async ({ intent, input, budget, pay_with }) => {
41
+ if (!hasWalletConfigured()) {
42
+ return text("No wallet configured. Set one up first:\n\n" +
43
+ ' wallet_setup({ action: "create", name: "my-wallet" })\n\n' +
44
+ "Then fund it with USDC on Tempo and try again.");
45
+ }
46
+ const method = pay_with ?? getConfiguredMethods()[0];
47
+ // Path 1: If authenticated, use the platform /solve route
48
+ try {
49
+ const result = await apiPost("/solve", {
50
+ intent,
51
+ input,
52
+ budget,
53
+ });
54
+ const jobId = result.job_id ?? "";
55
+ const agentId = result.agent_id ?? "";
56
+ if (result.feedback_token) {
57
+ storeFeedbackToken(jobId, result.feedback_token, agentId);
58
+ }
59
+ return text(formatRunResult(result) + ratingPrompt(jobId));
60
+ }
61
+ catch (err) {
62
+ const isAuthError = err instanceof Error &&
63
+ "status" in err &&
64
+ err.status === 401;
65
+ if (!isAuthError || !hasWalletConfigured())
66
+ throw err;
67
+ }
68
+ // Path 2: Wallet-only discovery — find agents, pick best, pay via MPP
69
+ const params = new URLSearchParams({ q: intent, limit: "5" });
70
+ const acceptedMethods = getAcceptedPaymentMethods();
71
+ if (acceptedMethods.length > 0) {
72
+ params.set("accepted_payment_methods", acceptedMethods.join(","));
73
+ }
74
+ const agents = await apiGet(`/agents?${params}`);
75
+ if (!agents || agents.length === 0) {
76
+ return text(`No agents found matching "${intent}".`);
77
+ }
78
+ // Show discovery results
79
+ const discovery = agentList(agents, intent);
80
+ // Pick cheapest agent within budget
81
+ const inputTokens = Math.ceil(JSON.stringify(input).length / 4);
82
+ const affordable = agents.filter((a) => {
83
+ const price = parseFloat(a.pricePer1kTokens ?? "0.01");
84
+ const cost = a.pricingModel === "fixed" ? price : (inputTokens / 1000) * price;
85
+ return cost <= budget;
86
+ });
87
+ const selected = affordable[0] ?? agents[0];
88
+ // Estimate cost for the selected agent
89
+ const selectedPrice = parseFloat(selected.pricePer1kTokens ?? "0.01");
90
+ const estimatedCost = selected.pricingModel === "fixed"
91
+ ? selectedPrice
92
+ : (inputTokens / 1000) * selectedPrice;
93
+ let result;
94
+ try {
95
+ result = await apiPostWithPayment(`/agents/${selected.id}/run`, { input }, method);
96
+ }
97
+ catch (err) {
98
+ const apiErr = err;
99
+ if (apiErr?.status === 402) {
100
+ return text("Payment failed — your wallet may not have enough USDC.\n\n" +
101
+ "Check your balance and fund your wallet, then try again.\n" +
102
+ "Use wallet_status to check your current payment methods.");
103
+ }
104
+ return text(`Error: ${apiErr?.message ?? "Failed to run agent"}`);
105
+ }
106
+ // Cache feedback token if present
107
+ const jobId = result.job_id ?? "";
108
+ const agentId2 = result.agent_id ?? selected.id;
109
+ if (result.feedback_token) {
110
+ storeFeedbackToken(jobId, result.feedback_token, agentId2);
111
+ }
112
+ // Combine discovery + result
113
+ const output = [
114
+ discovery,
115
+ "",
116
+ `Running ${selected.name} — best match`,
117
+ `Estimated cost: $${estimatedCost.toFixed(4)}`,
118
+ "",
119
+ formatRunResult(result, { paymentMethod: method }),
120
+ ratingPrompt(jobId),
121
+ ].join("\n");
122
+ return text(output);
123
+ });
124
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerTipTools(server: McpServer): void;
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ import { apiPost } from "../core/api-client.js";
3
+ import { getFeedbackToken } from "./_token-cache.js";
4
+ function text(t) {
5
+ return { content: [{ type: "text", text: t }] };
6
+ }
7
+ export function registerTipTools(server) {
8
+ server.tool("tip_agent", "Send a tip to an agent you've used. Tips help surface the best agents. Must be used within 1 hour of running the agent.", {
9
+ job_id: z.string().describe("Job ID from a completed execution"),
10
+ agent_id: z.string().describe("Agent ID to tip"),
11
+ amount: z.number().min(0.01).max(50).describe("Tip amount in USD"),
12
+ }, async ({ job_id, agent_id, amount }) => {
13
+ const tokenData = getFeedbackToken(job_id);
14
+ if (!tokenData) {
15
+ return text("No feedback token found for this job. You can only tip agents immediately after running them.");
16
+ }
17
+ // Use the resolved UUID from the run result (handles slugs)
18
+ const resolvedAgentId = tokenData.agent_id || agent_id;
19
+ try {
20
+ await apiPost("/tips", {
21
+ job_id,
22
+ agent_id: resolvedAgentId,
23
+ feedback_token: tokenData.token,
24
+ amount,
25
+ });
26
+ return text(`Tipped $${amount.toFixed(2)} for job ${job_id}. Thanks — tips help the best agents get discovered!`);
27
+ }
28
+ catch (err) {
29
+ const apiErr = err;
30
+ if (apiErr?.status === 401) {
31
+ return text("Feedback token expired. Tips must be submitted within 1 hour of running the agent.");
32
+ }
33
+ if (apiErr?.status === 429) {
34
+ return text("You can only tip this agent once every 30 days.");
35
+ }
36
+ const msg = apiErr?.body?.error || apiErr?.message || "Tip failed";
37
+ return text(`Could not send tip: ${msg}`);
38
+ }
39
+ });
40
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerWalletTools(server: McpServer): void;
@@ -0,0 +1,197 @@
1
+ import { z } from "zod";
2
+ import { getWallets, getCardConfig, addWallet } from "../core/config.js";
3
+ import { getWalletAddress } from "../core/payments.js";
4
+ import { isOwsAvailable, createOwsWallet, importKeyToOws, listOwsWallets, } from "../core/ows-adapter.js";
5
+ function text(t) {
6
+ return { content: [{ type: "text", text: t }] };
7
+ }
8
+ export function registerWalletTools(server) {
9
+ // ── wallet_status (extracted from check_wallet) ─────────────────
10
+ server.tool("wallet_status", "Check payment readiness. Shows all configured wallets, their chains, addresses, and card status.", {}, async () => {
11
+ const wallets = getWallets();
12
+ const card = getCardConfig();
13
+ if (wallets.length === 0 && !card) {
14
+ return text("No payment methods configured.\nUse wallet_setup to create or import a wallet.");
15
+ }
16
+ const lines = ["Payment methods:"];
17
+ for (const w of wallets) {
18
+ const addr = await getWalletAddress(w.id);
19
+ const label = w.label ? ` (${w.label})` : "";
20
+ const storage = w.keyType === "ows" ? " [encrypted]" : " [plaintext]";
21
+ lines.push(` ${w.id}${label}${storage}: ${w.chains.join(", ")} \u2014 ${addr ?? "unknown"}`);
22
+ }
23
+ if (card) {
24
+ lines.push(` Card: ${card.brand} ****${card.last4}`);
25
+ }
26
+ return text(lines.join("\n"));
27
+ });
28
+ // ── wallet_setup (NEW) ──────────────────────────────────────────
29
+ server.tool("wallet_setup", "Create or import a wallet for paying agents. Uses OWS (Open Wallet Standard) for encrypted key storage when available, falling back to plaintext.", {
30
+ action: z
31
+ .enum(["create", "import"])
32
+ .describe("'create' a new wallet or 'import' an existing private key"),
33
+ name: z
34
+ .string()
35
+ .optional()
36
+ .describe("Wallet name/label (default: auto-generated)"),
37
+ key: z
38
+ .string()
39
+ .optional()
40
+ .describe("Private key hex string (required for 'import', ignored for 'create')"),
41
+ chain: z.enum(["tempo", "base", "solana"]).optional()
42
+ .describe("Primary chain (default: tempo). Solana support is via Stripe deposit mode."),
43
+ }, async ({ action, name, key, chain }) => {
44
+ if (action === "import" && !key) {
45
+ return text("Error: 'key' parameter is required when action is 'import'.");
46
+ }
47
+ const selectedChains = chain === "solana" ? ["solana"] : ["tempo", "base"];
48
+ const defaultCh = chain ?? "tempo";
49
+ const owsReady = await isOwsAvailable();
50
+ if (action === "create") {
51
+ // Check for existing OWS wallets first
52
+ if (owsReady) {
53
+ const existing = await listOwsWallets();
54
+ if (existing.length > 0) {
55
+ const w = existing[0];
56
+ // Check if already in config
57
+ const wallets = getWallets();
58
+ const alreadyLinked = wallets.find(wl => wl.owsWalletId === w.id);
59
+ if (!alreadyLinked) {
60
+ addWallet({
61
+ id: w.name,
62
+ keyType: "ows",
63
+ owsWalletId: w.id,
64
+ chains: selectedChains,
65
+ defaultChain: defaultCh,
66
+ label: w.name,
67
+ });
68
+ }
69
+ return text([
70
+ "Found existing OWS wallet:",
71
+ ` Name: ${w.name}`,
72
+ ` Address: ${w.address}`,
73
+ ` Chains: ${selectedChains.join(", ")}`,
74
+ ` Storage: ~/.ows/ (encrypted)`,
75
+ "",
76
+ alreadyLinked ? "Already linked to Agent Wonderland." : "Linked to Agent Wonderland.",
77
+ "",
78
+ `Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
79
+ ].join("\n"));
80
+ }
81
+ }
82
+ if (!owsReady) {
83
+ return text("Cannot create wallet: OWS (Open Wallet Standard) is not installed.\n" +
84
+ "Install with: npm install -g @open-wallet-standard/core\n\n" +
85
+ "Alternatively, use action 'import' with an existing private key.");
86
+ }
87
+ const walletName = name ?? `aw-${Date.now()}`;
88
+ const result = await createOwsWallet(walletName);
89
+ // Persist to config so payment flows can find it
90
+ addWallet({
91
+ id: walletName,
92
+ keyType: "ows",
93
+ owsWalletId: result.walletId,
94
+ chains: selectedChains,
95
+ defaultChain: defaultCh,
96
+ label: walletName,
97
+ });
98
+ return text([
99
+ `Wallet created [encrypted]:`,
100
+ ` ID: ${result.walletId}`,
101
+ ` Address: ${result.address}`,
102
+ ` Name: ${walletName}`,
103
+ ` Chains: ${selectedChains.join(", ")}`,
104
+ ` Storage: ~/.ows/ (AES-256-GCM encrypted)`,
105
+ "",
106
+ `Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
107
+ "For testnet: npx mppx account fund",
108
+ ].join("\n"));
109
+ }
110
+ // action === "import"
111
+ if (owsReady) {
112
+ const walletName = name ?? `imported-${Date.now()}`;
113
+ const result = await importKeyToOws(key, walletName);
114
+ addWallet({
115
+ id: walletName,
116
+ keyType: "ows",
117
+ owsWalletId: result.walletId,
118
+ chains: selectedChains,
119
+ defaultChain: defaultCh,
120
+ label: walletName,
121
+ });
122
+ return text([
123
+ `Key imported to OWS [encrypted]:`,
124
+ ` ID: ${result.walletId}`,
125
+ ` Address: ${result.address}`,
126
+ ` Name: ${walletName}`,
127
+ ` Chains: ${selectedChains.join(", ")}`,
128
+ ].join("\n"));
129
+ }
130
+ // No OWS — store plaintext
131
+ try {
132
+ const { privateKeyToAccount } = await import("viem/accounts");
133
+ const normalizedKey = (key.startsWith("0x") ? key : `0x${key}`);
134
+ const account = privateKeyToAccount(normalizedKey);
135
+ const walletName = name ?? `wallet-${Date.now()}`;
136
+ addWallet({
137
+ id: walletName,
138
+ keyType: "evm",
139
+ key: normalizedKey,
140
+ chains: selectedChains,
141
+ defaultChain: defaultCh,
142
+ label: walletName,
143
+ });
144
+ return text([
145
+ `Key imported [plaintext] \u2014 OWS not available for encrypted storage.`,
146
+ ` Address: ${account.address}`,
147
+ ` Name: ${walletName}`,
148
+ ` Chains: ${selectedChains.join(", ")}`,
149
+ "",
150
+ "Install OWS for encrypted storage: npm install -g @open-wallet-standard/core",
151
+ ].join("\n"));
152
+ }
153
+ catch {
154
+ return text("Error: Invalid private key format.");
155
+ }
156
+ });
157
+ // ── wallet_set_policy (NEW) ─────────────────────────────────────
158
+ server.tool("wallet_set_policy", "Set spending limits on a wallet to control agent costs. Limits reset daily.", {
159
+ wallet_id: z.string().describe("Wallet ID to set policy on"),
160
+ max_per_tx: z
161
+ .number()
162
+ .positive()
163
+ .optional()
164
+ .describe("Maximum USD per transaction"),
165
+ max_per_day: z
166
+ .number()
167
+ .positive()
168
+ .optional()
169
+ .describe("Maximum USD per day across all transactions"),
170
+ }, async ({ wallet_id, max_per_tx, max_per_day }) => {
171
+ const wallets = getWallets();
172
+ const wallet = wallets.find((w) => w.id === wallet_id);
173
+ if (!wallet) {
174
+ const available = wallets.map((w) => w.id).join(", ") || "none";
175
+ return text(`Wallet "${wallet_id}" not found. Available wallets: ${available}`);
176
+ }
177
+ if (max_per_tx == null && max_per_day == null) {
178
+ return text("No policy changes specified. Provide max_per_tx and/or max_per_day.");
179
+ }
180
+ // Build policy summary
181
+ const policies = [];
182
+ if (max_per_tx != null) {
183
+ policies.push(`Max per transaction: $${max_per_tx.toFixed(2)}`);
184
+ }
185
+ if (max_per_day != null) {
186
+ policies.push(`Max per day: $${max_per_day.toFixed(2)}`);
187
+ }
188
+ // Note: actual persistence depends on the config module supporting policy fields.
189
+ // For now, we report what would be set. The core/config module will handle storage.
190
+ return text([
191
+ `Spending policy set for wallet "${wallet_id}":`,
192
+ ...policies.map((p) => ` ${p}`),
193
+ "",
194
+ "Policy will be enforced on all future transactions from this wallet.",
195
+ ].join("\n"));
196
+ });
197
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@agentwonderland/mcp",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "description": "MCP server for the Agent Wonderland AI agent marketplace",
6
+ "bin": {
7
+ "agentwonderland-mcp": "./dist/index.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./core": {
15
+ "types": "./dist/core/index.d.ts",
16
+ "default": "./dist/core/index.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "dev": "tsx src/index.ts",
21
+ "build": "tsc",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "dependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.12.1",
26
+ "mppx": "^0.4.9",
27
+ "viem": "^2.47.6",
28
+ "zod": "^3.24.0"
29
+ },
30
+ "peerDependencies": {
31
+ "@open-wallet-standard/core": "^1.0.0"
32
+ },
33
+ "peerDependenciesMeta": {
34
+ "@open-wallet-standard/core": {
35
+ "optional": true
36
+ }
37
+ },
38
+ "devDependencies": {
39
+ "tsx": "^4.0.0",
40
+ "typescript": "^5.7.0"
41
+ },
42
+ "keywords": [
43
+ "mcp",
44
+ "ai-agents",
45
+ "marketplace",
46
+ "payments",
47
+ "mpp"
48
+ ]
49
+ }
@@ -0,0 +1,114 @@
1
+ import { getApiUrl, getApiKey } from "./config.js";
2
+ import { getPaymentFetch } from "./payments.js";
3
+
4
+ // ── Error class ────────────────────────────────────────────────────
5
+
6
+ export class ApiError extends Error {
7
+ constructor(
8
+ public readonly status: number,
9
+ message: string,
10
+ public readonly body?: unknown,
11
+ ) {
12
+ super(message);
13
+ this.name = "ApiError";
14
+ }
15
+ }
16
+
17
+ // ── Internal helpers ───────────────────────────────────────────────
18
+
19
+ function buildHeaders(): Record<string, string> {
20
+ const headers: Record<string, string> = {
21
+ "Content-Type": "application/json",
22
+ Accept: "application/json",
23
+ };
24
+
25
+ const apiKey = getApiKey();
26
+ if (apiKey) {
27
+ headers["Authorization"] = `Bearer ${apiKey}`;
28
+ }
29
+
30
+ return headers;
31
+ }
32
+
33
+ async function handleResponse<T>(response: Response): Promise<T> {
34
+ let body: unknown;
35
+ const contentType = response.headers.get("content-type") ?? "";
36
+
37
+ if (contentType.includes("application/json")) {
38
+ body = await response.json();
39
+ } else {
40
+ body = await response.text();
41
+ }
42
+
43
+ if (!response.ok) {
44
+ const bodyObj = body && typeof body === "object" ? body as Record<string, unknown> : null;
45
+ const message = bodyObj?.error
46
+ ? String(bodyObj.error)
47
+ : bodyObj?.message
48
+ ? String(bodyObj.message)
49
+ : typeof body === "string"
50
+ ? body
51
+ : `Request failed with status ${response.status}`;
52
+ throw new ApiError(response.status, message, body);
53
+ }
54
+
55
+ return body as T;
56
+ }
57
+
58
+ // ── Public API ─────────────────────────────────────────────────────
59
+
60
+ export async function apiGet<T>(path: string): Promise<T> {
61
+ const url = `${getApiUrl()}${path}`;
62
+ const response = await fetch(url, {
63
+ method: "GET",
64
+ headers: buildHeaders(),
65
+ });
66
+ return handleResponse<T>(response);
67
+ }
68
+
69
+ export async function apiPost<T>(path: string, body: unknown): Promise<T> {
70
+ const url = `${getApiUrl()}${path}`;
71
+ const response = await fetch(url, {
72
+ method: "POST",
73
+ headers: buildHeaders(),
74
+ body: JSON.stringify(body),
75
+ });
76
+ return handleResponse<T>(response);
77
+ }
78
+
79
+ /**
80
+ * POST with payment support. Uses the configured payment method to
81
+ * auto-handle 402 → sign → retry for paid endpoints.
82
+ * Pass `payWith` to specify a method, or omit for auto-detection.
83
+ */
84
+ export async function apiPostWithPayment<T>(path: string, body: unknown, payWith?: string): Promise<T> {
85
+ const url = `${getApiUrl()}${path}`;
86
+ const paymentFetch = await getPaymentFetch(payWith);
87
+ const response = await paymentFetch(url, {
88
+ method: "POST",
89
+ headers: buildHeaders(),
90
+ body: JSON.stringify(body),
91
+ });
92
+ const result = await handleResponse<T>(response);
93
+
94
+ // mppx may strip extra fields from the response body during receipt processing.
95
+ // The gateway also sends feedback_token as a header to survive this.
96
+ if (result && typeof result === "object" && !("feedback_token" in result)) {
97
+ const headerToken = response.headers.get("x-feedback-token");
98
+ if (headerToken) {
99
+ (result as Record<string, unknown>).feedback_token = headerToken;
100
+ }
101
+ }
102
+
103
+ return result;
104
+ }
105
+
106
+ export async function apiPut<T>(path: string, body: unknown): Promise<T> {
107
+ const url = `${getApiUrl()}${path}`;
108
+ const response = await fetch(url, {
109
+ method: "PUT",
110
+ headers: buildHeaders(),
111
+ body: JSON.stringify(body),
112
+ });
113
+ return handleResponse<T>(response);
114
+ }