@agentwonderland/mcp 0.1.36 → 0.1.38

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.
@@ -45,6 +45,10 @@ describe("api-client headers", () => {
45
45
  Accept: "application/json",
46
46
  "X-AW-Consumer-Principal": "did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:42W2HfLfveSm1T5et9WTLp2CZ2QXdF2EYCUvyJ2gPpxF",
47
47
  "X-AW-Rebate-Principal": "did:pkh:eip155:8453:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
48
+ "X-AW-Surface": "mcp",
49
+ "X-AW-MCP-Version": "0.1.37",
50
+ "X-AW-MCP-Tool": "run_agent",
51
+ "X-AW-MCP-Action": "execute",
48
52
  }),
49
53
  }));
50
54
  });
@@ -53,20 +53,11 @@ describe("payment method initialization", () => {
53
53
  .mockReturnValueOnce({ fetch: createdFetches[2] })
54
54
  .mockReturnValueOnce({ fetch: createdFetches[3] });
55
55
  });
56
- it("rebuilds the cached card fetch when the card config changes", async () => {
56
+ it("rejects explicit card payment while card payments are launch-gated", async () => {
57
57
  const { getPaymentFetch } = await import("../payments.js");
58
- const firstFetch = await getPaymentFetch("card");
59
- currentCard = {
60
- consumerToken: "consumer_two",
61
- paymentMethodId: "pm_two",
62
- last4: "2222",
63
- brand: "mastercard",
64
- };
65
- const secondFetch = await getPaymentFetch("card");
66
- expect(firstFetch).not.toBe(secondFetch);
67
- expect(mockMppxCreate).toHaveBeenCalledTimes(2);
68
- expect(mockMppxCreate).toHaveBeenNthCalledWith(1, expect.objectContaining({ polyfill: false }));
69
- expect(mockMppxCreate).toHaveBeenNthCalledWith(2, expect.objectContaining({ polyfill: false }));
58
+ await expect(getPaymentFetch("card")).rejects.toThrow("Card payments are temporarily unavailable");
59
+ expect(mockMppxCreate).not.toHaveBeenCalled();
60
+ expect(mockStripe).not.toHaveBeenCalled();
70
61
  });
71
62
  it("initializes only the Base method when base is requested", async () => {
72
63
  const wallet = {
@@ -12,10 +12,32 @@ export class ApiError extends Error {
12
12
  this.name = "ApiError";
13
13
  }
14
14
  }
15
- async function buildHeaders(options) {
15
+ const MCP_VERSION = "0.1.37";
16
+ function inferToolHeaders(path, method) {
17
+ if (path === "/solve") {
18
+ return { "X-AW-MCP-Tool": "solve", "X-AW-MCP-Action": method === "POST" ? "execute" : "view" };
19
+ }
20
+ if (/^\/agents\/[^/]+\/run$/.test(path)) {
21
+ return { "X-AW-MCP-Tool": "run_agent", "X-AW-MCP-Action": method === "POST" ? "execute" : "view" };
22
+ }
23
+ if (path.startsWith("/agents?") || path === "/agents" || path === "/agents/search") {
24
+ return { "X-AW-MCP-Tool": "search_agents", "X-AW-MCP-Action": "search" };
25
+ }
26
+ if (/^\/agents\/[^/]+$/.test(path)) {
27
+ return { "X-AW-MCP-Tool": "get_agent", "X-AW-MCP-Action": "view" };
28
+ }
29
+ if (path.startsWith("/jobs")) {
30
+ return { "X-AW-MCP-Tool": "get_job", "X-AW-MCP-Action": "poll" };
31
+ }
32
+ return {};
33
+ }
34
+ async function buildHeaders(path, method, options) {
16
35
  const headers = {
17
36
  "Content-Type": "application/json",
18
37
  Accept: "application/json",
38
+ "X-AW-Surface": "mcp",
39
+ "X-AW-MCP-Version": MCP_VERSION,
40
+ ...inferToolHeaders(path, method),
19
41
  };
20
42
  const apiKey = getApiKey();
21
43
  if (apiKey) {
@@ -86,7 +108,7 @@ export async function apiGet(path, options) {
86
108
  const url = `${getApiUrl()}${path}`;
87
109
  const response = await fetch(url, {
88
110
  method: "GET",
89
- headers: await buildHeaders(options),
111
+ headers: await buildHeaders(path, "GET", options),
90
112
  });
91
113
  const result = await handleResponse(response);
92
114
  return attachResponseMetadata(result, response);
@@ -95,7 +117,7 @@ export async function apiPost(path, body, options) {
95
117
  const url = `${getApiUrl()}${path}`;
96
118
  const response = await fetch(url, {
97
119
  method: "POST",
98
- headers: await buildHeaders(options),
120
+ headers: await buildHeaders(path, "POST", options),
99
121
  body: JSON.stringify(body),
100
122
  });
101
123
  const result = await handleResponse(response);
@@ -111,7 +133,7 @@ export async function apiPostWithPayment(path, body, payWith, options) {
111
133
  const paymentFetch = await getPaymentFetch(payWith);
112
134
  const response = await paymentFetch(url, {
113
135
  method: "POST",
114
- headers: await buildHeaders({
136
+ headers: await buildHeaders(path, "POST", {
115
137
  ensureConsumerPrincipal: true,
116
138
  principalMethod: payWith,
117
139
  ...options,
@@ -125,7 +147,7 @@ export async function apiPut(path, body, options) {
125
147
  const url = `${getApiUrl()}${path}`;
126
148
  const response = await fetch(url, {
127
149
  method: "PUT",
128
- headers: await buildHeaders(options),
150
+ headers: await buildHeaders(path, "PUT", options),
129
151
  body: JSON.stringify(body),
130
152
  });
131
153
  const result = await handleResponse(response);
@@ -24,6 +24,10 @@ interface AgentLike {
24
24
  completedJobs?: number;
25
25
  avgRating?: number | null;
26
26
  };
27
+ provider?: {
28
+ name?: string;
29
+ slug?: string;
30
+ } | null;
27
31
  [key: string]: unknown;
28
32
  }
29
33
  export declare function agentLine(agent: AgentLike): string;
@@ -44,10 +44,11 @@ export function agentLine(agent) {
44
44
  const rating = agent.avgRating ?? agent.stats?.avgRating ?? null;
45
45
  const jobs = agent.stats?.completedJobs ?? agent.totalExecutions ?? 0;
46
46
  const price = formatPrice(agent.pricePerRunUsd);
47
+ const provider = agent.provider?.name ? ` by ${agent.provider.name}` : "";
47
48
  const reliability = agent.successRate != null && Number(agent.successRate) < 1
48
49
  ? ` • ${(Number(agent.successRate) * 100).toFixed(0)}% reliable`
49
50
  : "";
50
- return `${name}${slug} ${stars(rating)} ${compactNumber(jobs)} jobs • ${price}${reliability}`;
51
+ return `${name}${slug}${provider} ${stars(rating)} ${compactNumber(jobs)} jobs • ${price}${reliability}`;
51
52
  }
52
53
  export function formatLastActive(lastActiveAt) {
53
54
  if (!lastActiveAt)
@@ -2,6 +2,7 @@ import { Connection, PublicKey } from "@solana/web3.js";
2
2
  import type { WalletEntry } from "./config.js";
3
3
  export declare const SOLANA_USDC_MINT: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
4
4
  export declare const SOLANA_CHAIN_ID: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
5
+ export declare const SOLANA_CHAIN_ID_DEVNET: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
5
6
  interface SolanaChargeClientConfig {
6
7
  wallet: WalletEntry;
7
8
  rpcUrl?: string;
@@ -10,7 +10,9 @@ import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, createAssociatedTokenAcc
10
10
  import { toAtomicAmount } from "./amount-utils.js";
11
11
  export const SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
12
12
  export const SOLANA_CHAIN_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
13
+ export const SOLANA_CHAIN_ID_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
13
14
  const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
15
+ const SOLANA_RPC_DEVNET = "https://api.devnet.solana.com";
14
16
  const solanaChargeMethod = Method.from({
15
17
  name: "solana",
16
18
  intent: "charge",
@@ -83,13 +85,15 @@ export function solanaChargeClient(config) {
83
85
  return Method.toClient(solanaChargeMethod, {
84
86
  async createCredential({ challenge }) {
85
87
  const { request } = challenge;
88
+ const chainId = request.chainId ?? request.methodDetails?.chainId ?? SOLANA_CHAIN_ID;
89
+ const rpcUrl = config.rpcUrl ?? (chainId === SOLANA_CHAIN_ID_DEVNET ? SOLANA_RPC_DEVNET : SOLANA_RPC);
86
90
  const amount = toAtomicAmount(request.amount, request.decimals ?? 6);
87
91
  const decimals = request.decimals ?? 6;
88
92
  const mint = new PublicKey(request.currency ?? SOLANA_USDC_MINT);
89
93
  const recipient = new PublicKey(request.recipient);
90
94
  const keypair = await getKeypair(config.wallet);
91
95
  const owner = keypair.publicKey;
92
- const connection = new Connection(config.rpcUrl ?? SOLANA_RPC, "confirmed");
96
+ const connection = new Connection(rpcUrl, "confirmed");
93
97
  const sourceTokenAccount = getAssociatedTokenAddressSync(mint, owner, false, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID);
94
98
  const destination = await resolveRecipientTokenAccount(connection, mint, recipient);
95
99
  const instructions = [];
@@ -111,7 +115,7 @@ export function solanaChargeClient(config) {
111
115
  return Credential.serialize({
112
116
  challenge,
113
117
  payload: { signature, type: "signature" },
114
- source: `did:pkh:${SOLANA_CHAIN_ID}:${owner.toBase58()}`,
118
+ source: `did:pkh:${chainId}:${owner.toBase58()}`,
115
119
  });
116
120
  },
117
121
  });
@@ -18,6 +18,19 @@ export interface AgentRecord {
18
18
  ratingCount?: number;
19
19
  [key: string]: unknown;
20
20
  };
21
+ provider?: {
22
+ id?: string;
23
+ name?: string;
24
+ slug?: string;
25
+ website_url?: string | null;
26
+ } | null;
27
+ urls?: {
28
+ public_url?: string;
29
+ normalized_x402_url?: string;
30
+ mirrored_x402_url?: string | null;
31
+ agent_card_url?: string;
32
+ provider_url?: string | null;
33
+ };
21
34
  payment?: {
22
35
  pricing?: Record<string, unknown>;
23
36
  accepted_payments?: string[];
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ import { registerTipTools } from "./tools/tip.js";
14
14
  import { registerPassTools } from "./tools/passes.js";
15
15
  import { registerUploadTools } from "./tools/upload.js";
16
16
  import { registerProbeTools } from "./tools/probe.js";
17
+ import { registerProviderTools } from "./tools/providers.js";
17
18
  // ── Resources ────────────────────────────────────────────────────
18
19
  import { registerAgentResources } from "./resources/agents.js";
19
20
  import { registerWalletResources } from "./resources/wallet.js";
@@ -77,6 +78,7 @@ export async function startMcpServer() {
77
78
  registerPassTools(server);
78
79
  registerUploadTools(server);
79
80
  registerProbeTools(server);
81
+ registerProviderTools(server);
80
82
  // Register resources
81
83
  registerAgentResources(server);
82
84
  registerWalletResources(server);
@@ -56,6 +56,7 @@ vi.mock("../../core/config.js", () => ({
56
56
  }));
57
57
  vi.mock("../../core/payments.js", () => ({
58
58
  getWalletAddress: async () => null,
59
+ isCardPaymentEnabled: () => true,
59
60
  }));
60
61
  vi.mock("../../core/card-setup.js", () => ({
61
62
  formatCardSetupBlocks: () => state.cardSetupBlocks,
@@ -15,6 +15,8 @@ export function registerAgentInfoTools(server) {
15
15
  const _pricing = (payment.pricing ?? {});
16
16
  const creditPacks = payment.credit_packs;
17
17
  const totalJobs = (s.completedJobs ?? a.totalExecutions ?? 0);
18
+ const provider = a.provider ?? null;
19
+ const urls = a.urls ?? {};
18
20
  const acceptedPayments = payment.accepted_payments ?? [];
19
21
  const paymentLabelMap = {
20
22
  tempo_usdc: "tempo",
@@ -29,6 +31,7 @@ export function registerAgentInfoTools(server) {
29
31
  "",
30
32
  a.description ?? "",
31
33
  "",
34
+ ...(provider?.name ? [`Provider: ${provider.name}${provider.slug ? ` (${provider.slug})` : ""}`] : []),
32
35
  `Pricing: ${formatPrice(a.pricePerRunUsd)}`,
33
36
  ...(paymentLabels.length > 0 ? [`Accepted payments: ${paymentLabels.join(", ")}`] : []),
34
37
  ...(totalJobs > 0 && a.successRate != null
@@ -91,7 +94,7 @@ export function registerAgentInfoTools(server) {
91
94
  lines.push(` ${name}: ${def.type ?? "string"}${req}${desc}`);
92
95
  }
93
96
  }
94
- lines.push("", `ID: ${a.id}`, `View: ${agentWebUrl(a.id)}`);
97
+ lines.push("", `ID: ${a.id}`, `View: ${urls.public_url ?? agentWebUrl(a.id)}`, ...(urls.mirrored_x402_url ? [`Mirrored x402: ${urls.mirrored_x402_url}`] : []), ...(urls.normalized_x402_url ? [`Agent x402: ${urls.normalized_x402_url}`] : []), ...(urls.agent_card_url ? [`AgentCard: ${urls.agent_card_url}`] : []));
95
98
  return text(lines.join("\n"));
96
99
  });
97
100
  }
@@ -10,3 +10,4 @@ export { registerTipTools } from "./tip.js";
10
10
  export { registerPassTools } from "./passes.js";
11
11
  export { registerUploadTools } from "./upload.js";
12
12
  export { registerProbeTools } from "./probe.js";
13
+ export { registerProviderTools } from "./providers.js";
@@ -10,3 +10,4 @@ export { registerTipTools } from "./tip.js";
10
10
  export { registerPassTools } from "./passes.js";
11
11
  export { registerUploadTools } from "./upload.js";
12
12
  export { registerProbeTools } from "./probe.js";
13
+ export { registerProviderTools } from "./providers.js";
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerProviderTools(server: McpServer): void;
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ import { apiGet } from "../core/api-client.js";
3
+ import { agentLine } from "../core/formatters.js";
4
+ function text(t) {
5
+ return { content: [{ type: "text", text: t }] };
6
+ }
7
+ export function registerProviderTools(server) {
8
+ server.tool("search_providers", "Search API providers listed on Agent Wonderland.", {
9
+ query: z.string().optional().describe("Provider name or keyword"),
10
+ limit: z.number().int().min(1).max(25).default(10),
11
+ }, async ({ query, limit }) => {
12
+ const params = new URLSearchParams();
13
+ if (query)
14
+ params.set("q", query);
15
+ params.set("limit", String(limit));
16
+ const providers = await apiGet(`/providers?${params}`);
17
+ if (providers.length === 0)
18
+ return text(query ? `No providers found matching "${query}".` : "No providers found.");
19
+ return text([
20
+ `Found ${providers.length} provider${providers.length === 1 ? "" : "s"}:`,
21
+ "",
22
+ ...providers.map((provider) => ` ${provider.name} (${provider.slug}) • ${provider.stats?.live_endpoints ?? 0} endpoints`),
23
+ ].join("\n"));
24
+ });
25
+ server.tool("get_provider", "Get an API provider profile and its live payable endpoints.", {
26
+ provider: z.string().describe("Provider slug"),
27
+ limit: z.number().int().min(1).max(100).default(50),
28
+ }, async ({ provider, limit }) => {
29
+ const profile = await apiGet(`/providers/${provider}`);
30
+ const agents = await apiGet(`/providers/${provider}/agents?limit=${limit}`);
31
+ return text([
32
+ `${profile.name} (${profile.slug})`,
33
+ profile.description ?? "",
34
+ ...(profile.website_url ? [`Website: ${profile.website_url}`] : []),
35
+ `Live endpoints: ${profile.stats?.live_endpoints ?? agents.length}`,
36
+ "",
37
+ ...agents.map((agent) => ` ${agentLine(agent)}`),
38
+ ].filter(Boolean).join("\n"));
39
+ });
40
+ }
package/dist/tools/run.js CHANGED
@@ -87,7 +87,7 @@ function buildCreditPackOfferLines(agent) {
87
87
  export function registerRunTools(server) {
88
88
  server.tool("run_agent", "Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking. If spending confirmation is enabled, first call returns a price quote — call again with confirmed: true to execute. Local file paths in the input (e.g. /Users/.../photo.jpg) are automatically uploaded to temporary storage and replaced with download URLs before execution. If a file you need isn't on this MCP server's filesystem (e.g. a sandboxed /mnt/... attachment), call upload_file first to get a presigned upload URL, PUT the bytes to it, then pass the returned GET URL as input instead — that keeps the bytes out of the conversation context.", {
89
89
  agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
90
- input: z.record(z.unknown()).describe("Input payload for the agent"),
90
+ input: z.record(z.string(), z.unknown()).describe("Input payload for the agent"),
91
91
  pay_with: z.string().trim().min(1).optional().describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
92
92
  confirmed: z.boolean().optional().describe("Set to true to confirm spending after seeing the price quote."),
93
93
  }, async ({ agent_id, input, pay_with, confirmed }) => {
@@ -9,17 +9,23 @@ function text(t) {
9
9
  export function registerSearchTools(server) {
10
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
11
  query: z.string().optional().describe("Search query (natural language or keywords)"),
12
+ provider: z.string().optional().describe("Filter by API provider name or slug"),
13
+ provider_slug: z.string().optional().describe("Filter by exact API provider slug"),
12
14
  tag: z.string().optional().describe("Filter by tag (e.g. 'code', 'image', 'data')"),
13
15
  limit: z.number().optional().default(10).describe("Max results (1-50)"),
14
16
  max_price: z.number().optional().describe("Maximum price per request in USD"),
15
17
  min_rating: z.number().min(1).max(5).optional().describe("Minimum star rating (1-5)"),
16
18
  sort: z.enum(["relevance", "price", "rating", "popularity", "newest"]).optional()
17
19
  .describe("Sort results by: relevance (default), price, rating, popularity, or newest"),
18
- }, async ({ query, tag, limit, max_price, min_rating, sort }) => {
20
+ }, async ({ query, provider, provider_slug, tag, limit, max_price, min_rating, sort }) => {
19
21
  const requestedLimit = Math.max(1, Math.min(50, limit ?? 10));
20
22
  const params = new URLSearchParams();
21
23
  if (query)
22
24
  params.set("q", query);
25
+ if (provider_slug)
26
+ params.set("provider_slug", provider_slug);
27
+ else if (provider)
28
+ params.set("provider", provider);
23
29
  if (tag)
24
30
  params.set("tag", tag);
25
31
  // min_rating is filtered client-side on avgRating. Request extra candidates
@@ -110,7 +110,7 @@ export function registerSolveTools(server) {
110
110
  .string()
111
111
  .describe("What you want to accomplish (natural language)"),
112
112
  input: z
113
- .record(z.unknown())
113
+ .record(z.string(), z.unknown())
114
114
  .optional()
115
115
  .default({})
116
116
  .describe("Input payload for the agent"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentwonderland/mcp",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "type": "module",
5
5
  "description": "MCP server for the Agent Wonderland AI agent marketplace",
6
6
  "bin": {
@@ -28,7 +28,7 @@
28
28
  "mppx": "^0.5.10",
29
29
  "qrcode": "^1.5.4",
30
30
  "viem": "^2.47.6",
31
- "zod": "^3.24.0"
31
+ "zod": "^4.3.6"
32
32
  },
33
33
  "optionalDependencies": {
34
34
  "@open-wallet-standard/core": "^1.0.0"
@@ -71,6 +71,10 @@ describe("api-client headers", () => {
71
71
  "did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:42W2HfLfveSm1T5et9WTLp2CZ2QXdF2EYCUvyJ2gPpxF",
72
72
  "X-AW-Rebate-Principal":
73
73
  "did:pkh:eip155:8453:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
74
+ "X-AW-Surface": "mcp",
75
+ "X-AW-MCP-Version": "0.1.37",
76
+ "X-AW-MCP-Tool": "run_agent",
77
+ "X-AW-MCP-Action": "execute",
74
78
  }),
75
79
  }),
76
80
  );
@@ -63,28 +63,14 @@ describe("payment method initialization", () => {
63
63
  .mockReturnValueOnce({ fetch: createdFetches[3] });
64
64
  });
65
65
 
66
- it("rebuilds the cached card fetch when the card config changes", async () => {
66
+ it("rejects explicit card payment while card payments are launch-gated", async () => {
67
67
  const { getPaymentFetch } = await import("../payments.js");
68
68
 
69
- const firstFetch = await getPaymentFetch("card");
70
- currentCard = {
71
- consumerToken: "consumer_two",
72
- paymentMethodId: "pm_two",
73
- last4: "2222",
74
- brand: "mastercard",
75
- };
76
- const secondFetch = await getPaymentFetch("card");
77
-
78
- expect(firstFetch).not.toBe(secondFetch);
79
- expect(mockMppxCreate).toHaveBeenCalledTimes(2);
80
- expect(mockMppxCreate).toHaveBeenNthCalledWith(
81
- 1,
82
- expect.objectContaining({ polyfill: false }),
83
- );
84
- expect(mockMppxCreate).toHaveBeenNthCalledWith(
85
- 2,
86
- expect.objectContaining({ polyfill: false }),
69
+ await expect(getPaymentFetch("card")).rejects.toThrow(
70
+ "Card payments are temporarily unavailable",
87
71
  );
72
+ expect(mockMppxCreate).not.toHaveBeenCalled();
73
+ expect(mockStripe).not.toHaveBeenCalled();
88
74
  });
89
75
 
90
76
  it("initializes only the Base method when base is requested", async () => {
@@ -29,10 +29,34 @@ interface RequestOptions {
29
29
  extraHeaders?: Record<string, string>;
30
30
  }
31
31
 
32
- async function buildHeaders(options?: RequestOptions): Promise<Record<string, string>> {
32
+ const MCP_VERSION = "0.1.37";
33
+
34
+ function inferToolHeaders(path: string, method: string): Record<string, string> {
35
+ if (path === "/solve") {
36
+ return { "X-AW-MCP-Tool": "solve", "X-AW-MCP-Action": method === "POST" ? "execute" : "view" };
37
+ }
38
+ if (/^\/agents\/[^/]+\/run$/.test(path)) {
39
+ return { "X-AW-MCP-Tool": "run_agent", "X-AW-MCP-Action": method === "POST" ? "execute" : "view" };
40
+ }
41
+ if (path.startsWith("/agents?") || path === "/agents" || path === "/agents/search") {
42
+ return { "X-AW-MCP-Tool": "search_agents", "X-AW-MCP-Action": "search" };
43
+ }
44
+ if (/^\/agents\/[^/]+$/.test(path)) {
45
+ return { "X-AW-MCP-Tool": "get_agent", "X-AW-MCP-Action": "view" };
46
+ }
47
+ if (path.startsWith("/jobs")) {
48
+ return { "X-AW-MCP-Tool": "get_job", "X-AW-MCP-Action": "poll" };
49
+ }
50
+ return {};
51
+ }
52
+
53
+ async function buildHeaders(path: string, method: string, options?: RequestOptions): Promise<Record<string, string>> {
33
54
  const headers: Record<string, string> = {
34
55
  "Content-Type": "application/json",
35
56
  Accept: "application/json",
57
+ "X-AW-Surface": "mcp",
58
+ "X-AW-MCP-Version": MCP_VERSION,
59
+ ...inferToolHeaders(path, method),
36
60
  };
37
61
 
38
62
  const apiKey = getApiKey();
@@ -119,7 +143,7 @@ export async function apiGet<T>(path: string, options?: RequestOptions): Promise
119
143
  const url = `${getApiUrl()}${path}`;
120
144
  const response = await fetch(url, {
121
145
  method: "GET",
122
- headers: await buildHeaders(options),
146
+ headers: await buildHeaders(path, "GET", options),
123
147
  });
124
148
  const result = await handleResponse<T>(response);
125
149
  return attachResponseMetadata(result, response);
@@ -129,7 +153,7 @@ export async function apiPost<T>(path: string, body: unknown, options?: RequestO
129
153
  const url = `${getApiUrl()}${path}`;
130
154
  const response = await fetch(url, {
131
155
  method: "POST",
132
- headers: await buildHeaders(options),
156
+ headers: await buildHeaders(path, "POST", options),
133
157
  body: JSON.stringify(body),
134
158
  });
135
159
  const result = await handleResponse<T>(response);
@@ -151,7 +175,7 @@ export async function apiPostWithPayment<T>(
151
175
  const paymentFetch = await getPaymentFetch(payWith);
152
176
  const response = await paymentFetch(url, {
153
177
  method: "POST",
154
- headers: await buildHeaders({
178
+ headers: await buildHeaders(path, "POST", {
155
179
  ensureConsumerPrincipal: true,
156
180
  principalMethod: payWith,
157
181
  ...options,
@@ -166,7 +190,7 @@ export async function apiPut<T>(path: string, body: unknown, options?: RequestOp
166
190
  const url = `${getApiUrl()}${path}`;
167
191
  const response = await fetch(url, {
168
192
  method: "PUT",
169
- headers: await buildHeaders(options),
193
+ headers: await buildHeaders(path, "PUT", options),
170
194
  body: JSON.stringify(body),
171
195
  });
172
196
  const result = await handleResponse<T>(response);
@@ -53,6 +53,7 @@ interface AgentLike {
53
53
  totalExecutions?: number;
54
54
  pricePerRunUsd?: string;
55
55
  stats?: { completedJobs?: number; avgRating?: number | null };
56
+ provider?: { name?: string; slug?: string } | null;
56
57
  [key: string]: unknown;
57
58
  }
58
59
 
@@ -62,10 +63,11 @@ export function agentLine(agent: AgentLike): string {
62
63
  const rating = agent.avgRating ?? agent.stats?.avgRating ?? null;
63
64
  const jobs = agent.stats?.completedJobs ?? agent.totalExecutions ?? 0;
64
65
  const price = formatPrice(agent.pricePerRunUsd);
66
+ const provider = agent.provider?.name ? ` by ${agent.provider.name}` : "";
65
67
  const reliability = agent.successRate != null && Number(agent.successRate) < 1
66
68
  ? ` • ${(Number(agent.successRate) * 100).toFixed(0)}% reliable`
67
69
  : "";
68
- return `${name}${slug} ${stars(rating)} ${compactNumber(jobs)} jobs • ${price}${reliability}`;
70
+ return `${name}${slug}${provider} ${stars(rating)} ${compactNumber(jobs)} jobs • ${price}${reliability}`;
69
71
  }
70
72
 
71
73
  export function formatLastActive(lastActiveAt: string | null | undefined): string | null {
@@ -18,7 +18,9 @@ import { toAtomicAmount } from "./amount-utils.js";
18
18
 
19
19
  export const SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" as const;
20
20
  export const SOLANA_CHAIN_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" as const;
21
+ export const SOLANA_CHAIN_ID_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG" as const;
21
22
  const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
23
+ const SOLANA_RPC_DEVNET = "https://api.devnet.solana.com";
22
24
 
23
25
  const solanaChargeMethod = Method.from({
24
26
  name: "solana" as const,
@@ -116,13 +118,15 @@ export function solanaChargeClient(config: SolanaChargeClientConfig) {
116
118
  return Method.toClient(solanaChargeMethod as any, {
117
119
  async createCredential({ challenge }: any) {
118
120
  const { request } = challenge;
121
+ const chainId = request.chainId ?? request.methodDetails?.chainId ?? SOLANA_CHAIN_ID;
122
+ const rpcUrl = config.rpcUrl ?? (chainId === SOLANA_CHAIN_ID_DEVNET ? SOLANA_RPC_DEVNET : SOLANA_RPC);
119
123
  const amount = toAtomicAmount(request.amount, request.decimals ?? 6);
120
124
  const decimals = request.decimals ?? 6;
121
125
  const mint = new PublicKey(request.currency ?? SOLANA_USDC_MINT);
122
126
  const recipient = new PublicKey(request.recipient);
123
127
  const keypair = await getKeypair(config.wallet);
124
128
  const owner = keypair.publicKey;
125
- const connection = new Connection(config.rpcUrl ?? SOLANA_RPC, "confirmed");
129
+ const connection = new Connection(rpcUrl, "confirmed");
126
130
 
127
131
  const sourceTokenAccount = getAssociatedTokenAddressSync(
128
132
  mint,
@@ -177,7 +181,7 @@ export function solanaChargeClient(config: SolanaChargeClientConfig) {
177
181
  return Credential.serialize({
178
182
  challenge,
179
183
  payload: { signature, type: "signature" as const },
180
- source: `did:pkh:${SOLANA_CHAIN_ID}:${owner.toBase58()}`,
184
+ source: `did:pkh:${chainId}:${owner.toBase58()}`,
181
185
  });
182
186
  },
183
187
  });
package/src/core/types.ts CHANGED
@@ -19,6 +19,19 @@ export interface AgentRecord {
19
19
  ratingCount?: number;
20
20
  [key: string]: unknown;
21
21
  };
22
+ provider?: {
23
+ id?: string;
24
+ name?: string;
25
+ slug?: string;
26
+ website_url?: string | null;
27
+ } | null;
28
+ urls?: {
29
+ public_url?: string;
30
+ normalized_x402_url?: string;
31
+ mirrored_x402_url?: string | null;
32
+ agent_card_url?: string;
33
+ provider_url?: string | null;
34
+ };
22
35
  payment?: {
23
36
  pricing?: Record<string, unknown>;
24
37
  accepted_payments?: string[];
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ import { registerTipTools } from "./tools/tip.js";
16
16
  import { registerPassTools } from "./tools/passes.js";
17
17
  import { registerUploadTools } from "./tools/upload.js";
18
18
  import { registerProbeTools } from "./tools/probe.js";
19
+ import { registerProviderTools } from "./tools/providers.js";
19
20
 
20
21
  // ── Resources ────────────────────────────────────────────────────
21
22
  import { registerAgentResources } from "./resources/agents.js";
@@ -86,6 +87,7 @@ export async function startMcpServer(): Promise<void> {
86
87
  registerPassTools(server);
87
88
  registerUploadTools(server);
88
89
  registerProbeTools(server);
90
+ registerProviderTools(server);
89
91
 
90
92
  // Register resources
91
93
  registerAgentResources(server);
@@ -86,6 +86,7 @@ vi.mock("../../core/config.js", () => ({
86
86
 
87
87
  vi.mock("../../core/payments.js", () => ({
88
88
  getWalletAddress: async () => null,
89
+ isCardPaymentEnabled: () => true,
89
90
  }));
90
91
 
91
92
  vi.mock("../../core/card-setup.js", () => ({
@@ -32,6 +32,8 @@ export function registerAgentInfoTools(server: McpServer): void {
32
32
  } | null | undefined;
33
33
 
34
34
  const totalJobs = (s.completedJobs ?? a.totalExecutions ?? 0) as number;
35
+ const provider = a.provider ?? null;
36
+ const urls = a.urls ?? {};
35
37
  const acceptedPayments = (payment.accepted_payments as string[] | undefined) ?? [];
36
38
  const paymentLabelMap: Record<string, string> = {
37
39
  tempo_usdc: "tempo",
@@ -47,6 +49,7 @@ export function registerAgentInfoTools(server: McpServer): void {
47
49
  "",
48
50
  (a.description as string) ?? "",
49
51
  "",
52
+ ...(provider?.name ? [`Provider: ${provider.name}${provider.slug ? ` (${provider.slug})` : ""}`] : []),
50
53
  `Pricing: ${formatPrice(a.pricePerRunUsd)}`,
51
54
  ...(paymentLabels.length > 0 ? [`Accepted payments: ${paymentLabels.join(", ")}`] : []),
52
55
  ...(totalJobs > 0 && a.successRate != null
@@ -113,7 +116,14 @@ export function registerAgentInfoTools(server: McpServer): void {
113
116
  }
114
117
  }
115
118
 
116
- lines.push("", `ID: ${a.id}`, `View: ${agentWebUrl(a.id)}`);
119
+ lines.push(
120
+ "",
121
+ `ID: ${a.id}`,
122
+ `View: ${urls.public_url ?? agentWebUrl(a.id)}`,
123
+ ...(urls.mirrored_x402_url ? [`Mirrored x402: ${urls.mirrored_x402_url}`] : []),
124
+ ...(urls.normalized_x402_url ? [`Agent x402: ${urls.normalized_x402_url}`] : []),
125
+ ...(urls.agent_card_url ? [`AgentCard: ${urls.agent_card_url}`] : []),
126
+ );
117
127
  return text(lines.join("\n"));
118
128
  },
119
129
  );
@@ -10,3 +10,4 @@ export { registerTipTools } from "./tip.js";
10
10
  export { registerPassTools } from "./passes.js";
11
11
  export { registerUploadTools } from "./upload.js";
12
12
  export { registerProbeTools } from "./probe.js";
13
+ export { registerProviderTools } from "./providers.js";
@@ -0,0 +1,64 @@
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 { agentLine } 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
+ interface ProviderRecord {
12
+ id: string;
13
+ name: string;
14
+ slug: string;
15
+ description?: string | null;
16
+ website_url?: string | null;
17
+ logo_url?: string | null;
18
+ support_email?: string | null;
19
+ stats?: { live_endpoints?: number; total_executions?: number };
20
+ }
21
+
22
+ export function registerProviderTools(server: McpServer): void {
23
+ server.tool(
24
+ "search_providers",
25
+ "Search API providers listed on Agent Wonderland.",
26
+ {
27
+ query: z.string().optional().describe("Provider name or keyword"),
28
+ limit: z.number().int().min(1).max(25).default(10),
29
+ },
30
+ async ({ query, limit }) => {
31
+ const params = new URLSearchParams();
32
+ if (query) params.set("q", query);
33
+ params.set("limit", String(limit));
34
+ const providers = await apiGet<ProviderRecord[]>(`/providers?${params}`);
35
+ if (providers.length === 0) return text(query ? `No providers found matching "${query}".` : "No providers found.");
36
+ return text([
37
+ `Found ${providers.length} provider${providers.length === 1 ? "" : "s"}:`,
38
+ "",
39
+ ...providers.map((provider) => ` ${provider.name} (${provider.slug}) • ${provider.stats?.live_endpoints ?? 0} endpoints`),
40
+ ].join("\n"));
41
+ },
42
+ );
43
+
44
+ server.tool(
45
+ "get_provider",
46
+ "Get an API provider profile and its live payable endpoints.",
47
+ {
48
+ provider: z.string().describe("Provider slug"),
49
+ limit: z.number().int().min(1).max(100).default(50),
50
+ },
51
+ async ({ provider, limit }) => {
52
+ const profile = await apiGet<ProviderRecord>(`/providers/${provider}`);
53
+ const agents = await apiGet<AgentRecord[]>(`/providers/${provider}/agents?limit=${limit}`);
54
+ return text([
55
+ `${profile.name} (${profile.slug})`,
56
+ profile.description ?? "",
57
+ ...(profile.website_url ? [`Website: ${profile.website_url}`] : []),
58
+ `Live endpoints: ${profile.stats?.live_endpoints ?? agents.length}`,
59
+ "",
60
+ ...agents.map((agent) => ` ${agentLine(agent)}`),
61
+ ].filter(Boolean).join("\n"));
62
+ },
63
+ );
64
+ }
package/src/tools/run.ts CHANGED
@@ -126,7 +126,7 @@ export function registerRunTools(server: McpServer): void {
126
126
  "Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking. If spending confirmation is enabled, first call returns a price quote — call again with confirmed: true to execute. Local file paths in the input (e.g. /Users/.../photo.jpg) are automatically uploaded to temporary storage and replaced with download URLs before execution. If a file you need isn't on this MCP server's filesystem (e.g. a sandboxed /mnt/... attachment), call upload_file first to get a presigned upload URL, PUT the bytes to it, then pass the returned GET URL as input instead — that keeps the bytes out of the conversation context.",
127
127
  {
128
128
  agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
129
- input: z.record(z.unknown()).describe("Input payload for the agent"),
129
+ input: z.record(z.string(), z.unknown()).describe("Input payload for the agent"),
130
130
  pay_with: z.string().trim().min(1).optional().describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
131
131
  confirmed: z.boolean().optional().describe("Set to true to confirm spending after seeing the price quote."),
132
132
  },
@@ -16,6 +16,8 @@ export function registerSearchTools(server: McpServer): void {
16
16
  "Search the Agent Wonderland marketplace for AI agents. Returns a ranked list of matching agents with ratings, pricing, and job counts.",
17
17
  {
18
18
  query: z.string().optional().describe("Search query (natural language or keywords)"),
19
+ provider: z.string().optional().describe("Filter by API provider name or slug"),
20
+ provider_slug: z.string().optional().describe("Filter by exact API provider slug"),
19
21
  tag: z.string().optional().describe("Filter by tag (e.g. 'code', 'image', 'data')"),
20
22
  limit: z.number().optional().default(10).describe("Max results (1-50)"),
21
23
  max_price: z.number().optional().describe("Maximum price per request in USD"),
@@ -23,10 +25,12 @@ export function registerSearchTools(server: McpServer): void {
23
25
  sort: z.enum(["relevance", "price", "rating", "popularity", "newest"]).optional()
24
26
  .describe("Sort results by: relevance (default), price, rating, popularity, or newest"),
25
27
  },
26
- async ({ query, tag, limit, max_price, min_rating, sort }) => {
28
+ async ({ query, provider, provider_slug, tag, limit, max_price, min_rating, sort }) => {
27
29
  const requestedLimit = Math.max(1, Math.min(50, limit ?? 10));
28
30
  const params = new URLSearchParams();
29
31
  if (query) params.set("q", query);
32
+ if (provider_slug) params.set("provider_slug", provider_slug);
33
+ else if (provider) params.set("provider", provider);
30
34
  if (tag) params.set("tag", tag);
31
35
 
32
36
  // min_rating is filtered client-side on avgRating. Request extra candidates
@@ -143,7 +143,7 @@ export function registerSolveTools(server: McpServer): void {
143
143
  .string()
144
144
  .describe("What you want to accomplish (natural language)"),
145
145
  input: z
146
- .record(z.unknown())
146
+ .record(z.string(), z.unknown())
147
147
  .optional()
148
148
  .default({})
149
149
  .describe("Input payload for the agent"),