@rickydata/agent0-mcp 0.1.0

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 (70) hide show
  1. package/.env.example +17 -0
  2. package/Dockerfile +25 -0
  3. package/README.md +85 -0
  4. package/dist/auth/sdk-client.d.ts +47 -0
  5. package/dist/auth/sdk-client.js +142 -0
  6. package/dist/auth/sdk-client.js.map +1 -0
  7. package/dist/auth/token.d.ts +5 -0
  8. package/dist/auth/token.js +6 -0
  9. package/dist/auth/token.js.map +1 -0
  10. package/dist/auth/wallet-derivation.d.ts +26 -0
  11. package/dist/auth/wallet-derivation.js +76 -0
  12. package/dist/auth/wallet-derivation.js.map +1 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +140 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/tools/a2a.d.ts +3 -0
  17. package/dist/tools/a2a.js +170 -0
  18. package/dist/tools/a2a.js.map +1 -0
  19. package/dist/tools/discovery.d.ts +3 -0
  20. package/dist/tools/discovery.js +465 -0
  21. package/dist/tools/discovery.js.map +1 -0
  22. package/dist/tools/index.d.ts +3 -0
  23. package/dist/tools/index.js +38 -0
  24. package/dist/tools/index.js.map +1 -0
  25. package/dist/tools/payments.d.ts +3 -0
  26. package/dist/tools/payments.js +124 -0
  27. package/dist/tools/payments.js.map +1 -0
  28. package/dist/tools/registration.d.ts +3 -0
  29. package/dist/tools/registration.js +324 -0
  30. package/dist/tools/registration.js.map +1 -0
  31. package/dist/tools/reputation.d.ts +3 -0
  32. package/dist/tools/reputation.js +147 -0
  33. package/dist/tools/reputation.js.map +1 -0
  34. package/dist/utils/chains.d.ts +10 -0
  35. package/dist/utils/chains.js +33 -0
  36. package/dist/utils/chains.js.map +1 -0
  37. package/dist/utils/trust-labels.d.ts +10 -0
  38. package/dist/utils/trust-labels.js +48 -0
  39. package/dist/utils/trust-labels.js.map +1 -0
  40. package/dist/utils/validation.d.ts +12 -0
  41. package/dist/utils/validation.js +19 -0
  42. package/dist/utils/validation.js.map +1 -0
  43. package/package.json +32 -0
  44. package/src/auth/sdk-client.ts +171 -0
  45. package/src/auth/token.ts +19 -0
  46. package/src/auth/wallet-derivation.ts +91 -0
  47. package/src/index.ts +184 -0
  48. package/src/tools/a2a.ts +205 -0
  49. package/src/tools/discovery.ts +517 -0
  50. package/src/tools/index.ts +45 -0
  51. package/src/tools/payments.ts +146 -0
  52. package/src/tools/registration.ts +389 -0
  53. package/src/tools/reputation.ts +183 -0
  54. package/src/utils/chains.ts +42 -0
  55. package/src/utils/trust-labels.ts +53 -0
  56. package/src/utils/validation.ts +20 -0
  57. package/tests/a2a.test.ts +234 -0
  58. package/tests/chains.test.ts +57 -0
  59. package/tests/discovery.test.ts +455 -0
  60. package/tests/e2e.test.ts +234 -0
  61. package/tests/payments.test.ts +148 -0
  62. package/tests/registration.test.ts +313 -0
  63. package/tests/reputation.test.ts +231 -0
  64. package/tests/sdk-client.test.ts +143 -0
  65. package/tests/tool-router.test.ts +28 -0
  66. package/tests/trust-labels.test.ts +229 -0
  67. package/tests/validation.test.ts +132 -0
  68. package/tests/wallet-derivation.test.ts +109 -0
  69. package/tsconfig.json +8 -0
  70. package/vitest.config.ts +8 -0
@@ -0,0 +1,517 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import { SDK } from "agent0-sdk";
3
+ import { computeTrustLabel } from "../utils/trust-labels.js";
4
+ import { getChainName, CHAINS } from "../utils/chains.js";
5
+
6
+ // ============================================================================
7
+ // SDK INITIALIZATION (read-only, no private key needed)
8
+ // ============================================================================
9
+
10
+ const DEFAULT_CHAIN_ID = parseInt(process.env.AGENT0_CHAIN_ID || "11155111", 10);
11
+
12
+ function getReadOnlySDK(chainId?: number): SDK {
13
+ return new SDK({ chainId: chainId ?? DEFAULT_CHAIN_ID });
14
+ }
15
+
16
+ // ============================================================================
17
+ // RESPONSE HELPERS
18
+ // ============================================================================
19
+
20
+ function formatAgentSummary(agent: Record<string, unknown>): Record<string, unknown> {
21
+ const count = (agent.feedbackCount as number) ?? 0;
22
+ const avg = (agent.averageValue as number) ?? 0;
23
+ const trust = computeTrustLabel(count, avg);
24
+
25
+ return {
26
+ agentId: agent.agentId,
27
+ chainId: agent.chainId,
28
+ name: agent.name,
29
+ description: agent.description,
30
+ image: agent.image,
31
+ active: agent.active,
32
+ x402support: agent.x402support,
33
+ owners: agent.owners,
34
+ walletAddress: agent.walletAddress,
35
+ mcp: agent.mcp || null,
36
+ a2a: agent.a2a || null,
37
+ web: agent.web || null,
38
+ ens: agent.ens || null,
39
+ mcpTools: agent.mcpTools,
40
+ a2aSkills: agent.a2aSkills,
41
+ oasfSkills: agent.oasfSkills,
42
+ oasfDomains: agent.oasfDomains,
43
+ supportedTrusts: agent.supportedTrusts,
44
+ trust: trust.display,
45
+ feedbackCount: count,
46
+ averageValue: avg,
47
+ updatedAt: agent.updatedAt,
48
+ };
49
+ }
50
+
51
+ // ============================================================================
52
+ // TOOL DEFINITIONS
53
+ // ============================================================================
54
+
55
+ export const discoveryTools: Tool[] = [
56
+ {
57
+ name: "search_agents",
58
+ description:
59
+ "Search for ERC-8004 registered AI agents by name, capabilities, tools, skills, or reputation. " +
60
+ "Returns agent summaries with trust labels. Supports multi-chain search.",
61
+ inputSchema: {
62
+ type: "object" as const,
63
+ properties: {
64
+ name: {
65
+ type: "string",
66
+ description: "Substring match on agent name",
67
+ },
68
+ keyword: {
69
+ type: "string",
70
+ description: "Keyword search across agent metadata",
71
+ },
72
+ mcpTools: {
73
+ type: "array",
74
+ items: { type: "string" },
75
+ description: "Filter by MCP tool names the agent exposes",
76
+ },
77
+ a2aSkills: {
78
+ type: "array",
79
+ items: { type: "string" },
80
+ description: "Filter by A2A skills",
81
+ },
82
+ oasfSkills: {
83
+ type: "array",
84
+ items: { type: "string" },
85
+ description: "Filter by OASF taxonomy skills",
86
+ },
87
+ oasfDomains: {
88
+ type: "array",
89
+ items: { type: "string" },
90
+ description: "Filter by OASF taxonomy domains",
91
+ },
92
+ active: {
93
+ type: "boolean",
94
+ description: "Filter by active status (default: true)",
95
+ },
96
+ x402support: {
97
+ type: "boolean",
98
+ description: "Filter by x402 payment support",
99
+ },
100
+ hasMCP: {
101
+ type: "boolean",
102
+ description: "Only agents with an MCP endpoint",
103
+ },
104
+ hasA2A: {
105
+ type: "boolean",
106
+ description: "Only agents with an A2A endpoint",
107
+ },
108
+ chains: {
109
+ type: "array",
110
+ items: { type: "number" },
111
+ description:
112
+ "Chain IDs to search (default: [11155111]). Use [1, 8453, 137, 11155111] for all supported chains.",
113
+ },
114
+ minFeedbackValue: {
115
+ type: "number",
116
+ description: "Minimum average feedback score (0-100)",
117
+ },
118
+ limit: {
119
+ type: "number",
120
+ description: "Max results to return (default: 20, max: 100)",
121
+ },
122
+ },
123
+ },
124
+ },
125
+ {
126
+ name: "get_agent",
127
+ description:
128
+ "Get detailed information about a specific ERC-8004 agent by its ID (format: chainId:tokenId, e.g. '11155111:42'). " +
129
+ "Returns full agent profile with endpoints, tools, skills, trust label, and reputation summary.",
130
+ inputSchema: {
131
+ type: "object" as const,
132
+ properties: {
133
+ agentId: {
134
+ type: "string",
135
+ description:
136
+ "Agent ID in chainId:tokenId format (e.g. '11155111:42' or '1:123')",
137
+ },
138
+ },
139
+ required: ["agentId"],
140
+ },
141
+ },
142
+ {
143
+ name: "get_supported_chains",
144
+ description:
145
+ "List all blockchain networks that support ERC-8004 agent registration. " +
146
+ "Returns chain IDs, names, registry contract addresses, and subgraph URLs.",
147
+ inputSchema: {
148
+ type: "object" as const,
149
+ properties: {},
150
+ },
151
+ },
152
+ {
153
+ name: "get_platform_stats",
154
+ description:
155
+ "Get aggregate statistics about the ERC-8004 agent ecosystem on a specific chain. " +
156
+ "Returns total agents, active count, agents with MCP/A2A endpoints, and x402 support count.",
157
+ inputSchema: {
158
+ type: "object" as const,
159
+ properties: {
160
+ chainId: {
161
+ type: "number",
162
+ description:
163
+ "Chain ID to query (default: 11155111 Sepolia). Use 1 for Ethereum Mainnet, 8453 for Base.",
164
+ },
165
+ },
166
+ },
167
+ },
168
+ {
169
+ name: "get_reputation_summary",
170
+ description:
171
+ "Get reputation summary (feedback count and average value) for a specific agent. " +
172
+ "Optionally filter by tag. Returns trust label computation.",
173
+ inputSchema: {
174
+ type: "object" as const,
175
+ properties: {
176
+ agentId: {
177
+ type: "string",
178
+ description:
179
+ "Agent ID in chainId:tokenId format (e.g. '11155111:42')",
180
+ },
181
+ tag: {
182
+ type: "string",
183
+ description: "Optional tag to filter reputation by (e.g. 'enterprise', 'quality')",
184
+ },
185
+ },
186
+ required: ["agentId"],
187
+ },
188
+ },
189
+ {
190
+ name: "search_feedback",
191
+ description:
192
+ "Search feedback/reviews for agents. Can search by agent ID, reviewer wallet, tags, " +
193
+ "or across multiple agents. Returns individual feedback entries with scores and metadata.",
194
+ inputSchema: {
195
+ type: "object" as const,
196
+ properties: {
197
+ agentId: {
198
+ type: "string",
199
+ description: "Agent ID to search feedback for (chainId:tokenId format)",
200
+ },
201
+ agents: {
202
+ type: "array",
203
+ items: { type: "string" },
204
+ description: "Multiple agent IDs to search feedback across",
205
+ },
206
+ reviewers: {
207
+ type: "array",
208
+ items: { type: "string" },
209
+ description: "Filter by reviewer wallet addresses",
210
+ },
211
+ tags: {
212
+ type: "array",
213
+ items: { type: "string" },
214
+ description: "Filter by feedback tags",
215
+ },
216
+ minValue: {
217
+ type: "number",
218
+ description: "Minimum feedback value (0-100)",
219
+ },
220
+ maxValue: {
221
+ type: "number",
222
+ description: "Maximum feedback value (0-100)",
223
+ },
224
+ includeRevoked: {
225
+ type: "boolean",
226
+ description: "Include revoked feedback (default: false)",
227
+ },
228
+ },
229
+ },
230
+ },
231
+ ];
232
+
233
+ // ============================================================================
234
+ // TOOL HANDLERS
235
+ // ============================================================================
236
+
237
+ async function handleSearchAgents(
238
+ args: Record<string, unknown>,
239
+ ): Promise<unknown> {
240
+ const limit = Math.min(
241
+ Math.max(1, (args.limit as number) ?? 20),
242
+ 100,
243
+ );
244
+
245
+ const chainId = (args.chains as number[] | undefined)?.[0] ?? DEFAULT_CHAIN_ID;
246
+ const sdk = getReadOnlySDK(chainId);
247
+
248
+ const filters: Record<string, unknown> = {};
249
+ if (args.name) filters.name = args.name;
250
+ if (args.keyword) filters.keyword = args.keyword;
251
+ if (args.mcpTools) filters.mcpTools = args.mcpTools;
252
+ if (args.a2aSkills) filters.a2aSkills = args.a2aSkills;
253
+ if (args.oasfSkills) filters.oasfSkills = args.oasfSkills;
254
+ if (args.oasfDomains) filters.oasfDomains = args.oasfDomains;
255
+ if (args.active !== undefined) filters.active = args.active;
256
+ if (args.x402support !== undefined) filters.x402support = args.x402support;
257
+ if (args.hasMCP !== undefined) filters.hasMCP = args.hasMCP;
258
+ if (args.hasA2A !== undefined) filters.hasA2A = args.hasA2A;
259
+ if (args.chains) filters.chains = args.chains;
260
+ if (args.minFeedbackValue) {
261
+ filters.feedback = {
262
+ minValue: args.minFeedbackValue,
263
+ includeRevoked: false,
264
+ };
265
+ }
266
+
267
+ const results = await sdk.searchAgents(
268
+ filters,
269
+ { sort: ["updatedAt:desc"] },
270
+ );
271
+
272
+ const agents = results.slice(0, limit).map((a) =>
273
+ formatAgentSummary(a as unknown as Record<string, unknown>),
274
+ );
275
+
276
+ return {
277
+ count: agents.length,
278
+ totalAvailable: results.length,
279
+ chainId,
280
+ agents,
281
+ };
282
+ }
283
+
284
+ async function handleGetAgent(
285
+ args: Record<string, unknown>,
286
+ ): Promise<unknown> {
287
+ const agentId = args.agentId as string;
288
+ if (!agentId) return { error: "agentId is required (format: chainId:tokenId)" };
289
+
290
+ const parts = agentId.split(":");
291
+ if (parts.length !== 2) {
292
+ return { error: "Invalid agentId format. Use chainId:tokenId (e.g. '11155111:42')" };
293
+ }
294
+
295
+ const chainId = parseInt(parts[0], 10);
296
+ const sdk = getReadOnlySDK(chainId);
297
+ const agent = await sdk.getAgent(agentId);
298
+
299
+ if (!agent) {
300
+ return { error: `Agent ${agentId} not found` };
301
+ }
302
+
303
+ // Get reputation summary
304
+ let reputation = { count: 0, averageValue: 0 };
305
+ try {
306
+ reputation = await sdk.getReputationSummary(agentId);
307
+ } catch {
308
+ // Reputation may not be available for all agents
309
+ }
310
+
311
+ const trust = computeTrustLabel(reputation.count, reputation.averageValue);
312
+ const summary = formatAgentSummary(agent as unknown as Record<string, unknown>);
313
+
314
+ return {
315
+ ...summary,
316
+ reputation: {
317
+ count: reputation.count,
318
+ averageValue: reputation.averageValue,
319
+ trustLabel: trust.label,
320
+ trustDisplay: trust.display,
321
+ },
322
+ chain: getChainName(chainId),
323
+ };
324
+ }
325
+
326
+ async function handleGetSupportedChains(): Promise<unknown> {
327
+ const chains = [
328
+ {
329
+ chainId: 1,
330
+ name: "Ethereum Mainnet",
331
+ identity: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
332
+ reputation: "0x8004BAa17C55a88189AE136b182e5fdA19dE9b63",
333
+ hasSubgraph: true,
334
+ },
335
+ {
336
+ chainId: 8453,
337
+ name: "Base",
338
+ identity: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
339
+ reputation: "0x8004BAa17C55a88189AE136b182e5fdA19dE9b63",
340
+ hasSubgraph: true,
341
+ },
342
+ {
343
+ chainId: 137,
344
+ name: "Polygon Mainnet",
345
+ identity: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
346
+ reputation: "0x8004BAa17C55a88189AE136b182e5fdA19dE9b63",
347
+ hasSubgraph: true,
348
+ },
349
+ {
350
+ chainId: 11155111,
351
+ name: "Ethereum Sepolia (Testnet)",
352
+ identity: "0x8004A818BFB912233c491871b3d84c89A494BD9e",
353
+ reputation: "0x8004B663056A597Dffe9eCcC1965A193B7388713",
354
+ hasSubgraph: true,
355
+ },
356
+ {
357
+ chainId: 84532,
358
+ name: "Base Sepolia (Testnet)",
359
+ identity: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
360
+ reputation: "0x8004BAa17C55a88189AE136b182e5fdA19dE9b63",
361
+ hasSubgraph: true,
362
+ },
363
+ ];
364
+
365
+ return {
366
+ count: chains.length,
367
+ chains,
368
+ note: "Use chainId:tokenId format when querying agents (e.g. '1:42' for Ethereum Mainnet agent #42)",
369
+ };
370
+ }
371
+
372
+ async function handleGetPlatformStats(
373
+ args: Record<string, unknown>,
374
+ ): Promise<unknown> {
375
+ const chainId = (args.chainId as number) ?? DEFAULT_CHAIN_ID;
376
+ const sdk = getReadOnlySDK(chainId);
377
+
378
+ // Fetch all agents on this chain (no filter)
379
+ const allAgents = await sdk.searchAgents({}, { sort: ["updatedAt:desc"] });
380
+
381
+ let activeCount = 0;
382
+ let mcpCount = 0;
383
+ let a2aCount = 0;
384
+ let x402Count = 0;
385
+ let withFeedback = 0;
386
+ let totalTools = 0;
387
+ let totalSkills = 0;
388
+
389
+ for (const agent of allAgents) {
390
+ if (agent.active) activeCount++;
391
+ if (agent.mcp) mcpCount++;
392
+ if (agent.a2a) a2aCount++;
393
+ if (agent.x402support) x402Count++;
394
+ if (agent.feedbackCount && agent.feedbackCount > 0) withFeedback++;
395
+ totalTools += agent.mcpTools?.length ?? 0;
396
+ totalSkills += agent.a2aSkills?.length ?? 0;
397
+ }
398
+
399
+ return {
400
+ chainId,
401
+ chain: getChainName(chainId),
402
+ totalAgents: allAgents.length,
403
+ activeAgents: activeCount,
404
+ agentsWithMCP: mcpCount,
405
+ agentsWithA2A: a2aCount,
406
+ agentsWithX402: x402Count,
407
+ agentsWithFeedback: withFeedback,
408
+ totalMCPTools: totalTools,
409
+ totalA2ASkills: totalSkills,
410
+ };
411
+ }
412
+
413
+ async function handleGetReputationSummary(
414
+ args: Record<string, unknown>,
415
+ ): Promise<unknown> {
416
+ const agentId = args.agentId as string;
417
+ if (!agentId) return { error: "agentId is required" };
418
+
419
+ const parts = agentId.split(":");
420
+ if (parts.length !== 2) {
421
+ return { error: "Invalid agentId format. Use chainId:tokenId" };
422
+ }
423
+
424
+ const chainId = parseInt(parts[0], 10);
425
+ const sdk = getReadOnlySDK(chainId);
426
+ const tag = args.tag as string | undefined;
427
+
428
+ const reputation = await sdk.getReputationSummary(agentId, tag);
429
+ const trust = computeTrustLabel(reputation.count, reputation.averageValue);
430
+
431
+ return {
432
+ agentId,
433
+ chain: getChainName(chainId),
434
+ count: reputation.count,
435
+ averageValue: reputation.averageValue,
436
+ trustLabel: trust.label,
437
+ trustEmoji: trust.emoji,
438
+ trustDisplay: trust.display,
439
+ tag: tag ?? null,
440
+ };
441
+ }
442
+
443
+ async function handleSearchFeedback(
444
+ args: Record<string, unknown>,
445
+ ): Promise<unknown> {
446
+ const agentId = args.agentId as string | undefined;
447
+ const agents = args.agents as string[] | undefined;
448
+
449
+ // Determine chain from first agent ID available
450
+ const firstId = agentId ?? agents?.[0];
451
+ if (!firstId) {
452
+ return { error: "Provide agentId or agents[] to search feedback" };
453
+ }
454
+
455
+ const parts = firstId.split(":");
456
+ const chainId = parts.length === 2 ? parseInt(parts[0], 10) : DEFAULT_CHAIN_ID;
457
+ const sdk = getReadOnlySDK(chainId);
458
+
459
+ const filters: Record<string, unknown> = {};
460
+ if (agentId) filters.agentId = agentId;
461
+ if (agents) filters.agents = agents;
462
+ if (args.reviewers) filters.reviewers = args.reviewers;
463
+ if (args.tags) filters.tags = args.tags;
464
+ if (args.includeRevoked !== undefined)
465
+ filters.includeRevoked = args.includeRevoked;
466
+
467
+ const options: Record<string, unknown> = {};
468
+ if (args.minValue !== undefined) options.minValue = args.minValue;
469
+ if (args.maxValue !== undefined) options.maxValue = args.maxValue;
470
+
471
+ const feedbacks = await sdk.searchFeedback(
472
+ filters as Parameters<typeof sdk.searchFeedback>[0],
473
+ options as Parameters<typeof sdk.searchFeedback>[1],
474
+ );
475
+
476
+ return {
477
+ count: feedbacks.length,
478
+ feedbacks: feedbacks.slice(0, 50).map((f) => ({
479
+ agentId: f.agentId,
480
+ reviewer: f.reviewer,
481
+ value: f.value,
482
+ tags: f.tags,
483
+ text: f.text,
484
+ endpoint: f.endpoint,
485
+ isRevoked: f.isRevoked,
486
+ createdAt: f.createdAt,
487
+ mcpTool: f.mcpTool,
488
+ a2aSkills: f.a2aSkills,
489
+ })),
490
+ };
491
+ }
492
+
493
+ // ============================================================================
494
+ // DISPATCH
495
+ // ============================================================================
496
+
497
+ export async function handleDiscoveryTool(
498
+ name: string,
499
+ args: Record<string, unknown>,
500
+ ): Promise<unknown> {
501
+ switch (name) {
502
+ case "search_agents":
503
+ return handleSearchAgents(args);
504
+ case "get_agent":
505
+ return handleGetAgent(args);
506
+ case "get_supported_chains":
507
+ return handleGetSupportedChains();
508
+ case "get_platform_stats":
509
+ return handleGetPlatformStats(args);
510
+ case "get_reputation_summary":
511
+ return handleGetReputationSummary(args);
512
+ case "search_feedback":
513
+ return handleSearchFeedback(args);
514
+ default:
515
+ return { error: `Unknown discovery tool: ${name}` };
516
+ }
517
+ }
@@ -0,0 +1,45 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import { discoveryTools, handleDiscoveryTool } from "./discovery.js";
3
+ import { reputationTools, handleReputationTool } from "./reputation.js";
4
+ import { registrationTools, handleRegistrationTool } from "./registration.js";
5
+ import { paymentsTools, handlePaymentsTool } from "./payments.js";
6
+ import { a2aTools, handleA2ATool } from "./a2a.js";
7
+
8
+ // Aggregate all tool definitions
9
+ export const TOOLS: Tool[] = [
10
+ ...discoveryTools,
11
+ ...reputationTools,
12
+ ...registrationTools,
13
+ ...paymentsTools,
14
+ ...a2aTools,
15
+ ];
16
+
17
+ // Tool name -> handler routing map
18
+ const TOOL_HANDLERS: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {};
19
+
20
+ for (const tool of discoveryTools) {
21
+ TOOL_HANDLERS[tool.name] = (args) => handleDiscoveryTool(tool.name, args);
22
+ }
23
+ for (const tool of reputationTools) {
24
+ TOOL_HANDLERS[tool.name] = (args) => handleReputationTool(tool.name, args);
25
+ }
26
+ for (const tool of registrationTools) {
27
+ TOOL_HANDLERS[tool.name] = (args) => handleRegistrationTool(tool.name, args);
28
+ }
29
+ for (const tool of paymentsTools) {
30
+ TOOL_HANDLERS[tool.name] = (args) => handlePaymentsTool(tool.name, args);
31
+ }
32
+ for (const tool of a2aTools) {
33
+ TOOL_HANDLERS[tool.name] = (args) => handleA2ATool(tool.name, args);
34
+ }
35
+
36
+ export async function handleToolCall(
37
+ name: string,
38
+ args: Record<string, unknown>,
39
+ ): Promise<unknown> {
40
+ const handler = TOOL_HANDLERS[name];
41
+ if (!handler) {
42
+ return { error: `Unknown tool: ${name}` };
43
+ }
44
+ return handler(args);
45
+ }
@@ -0,0 +1,146 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import { isX402Required } from "agent0-sdk";
3
+ import {
4
+ getAuthenticatedSDK,
5
+ hasAuthentication,
6
+ } from "../auth/sdk-client.js";
7
+
8
+ export const paymentsTools: Tool[] = [
9
+ {
10
+ name: "x402_request",
11
+ description:
12
+ "Make an HTTP request with built-in x402 payment handling. " +
13
+ "If the server returns 402 Payment Required, inspects payment requirements and optionally pays. " +
14
+ "Requires configured wallet with funds on the appropriate chain.",
15
+ inputSchema: {
16
+ type: "object" as const,
17
+ properties: {
18
+ url: {
19
+ type: "string",
20
+ description: "The URL to request",
21
+ },
22
+ method: {
23
+ type: "string",
24
+ enum: ["GET", "POST", "PUT", "DELETE"],
25
+ description: "HTTP method (default: GET)",
26
+ },
27
+ headers: {
28
+ type: "object",
29
+ description: "Additional request headers (key-value pairs)",
30
+ },
31
+ body: {
32
+ type: "string",
33
+ description: "Request body (for POST/PUT)",
34
+ },
35
+ autoPay: {
36
+ type: "boolean",
37
+ description:
38
+ "Automatically pay and retry on 402 (default: false). " +
39
+ "When false, returns payment requirements for inspection.",
40
+ },
41
+ maxPaymentUsd: {
42
+ type: "number",
43
+ description:
44
+ "Maximum payment amount in USD (safety limit, default: 1.00)",
45
+ },
46
+ },
47
+ required: ["url"],
48
+ },
49
+ },
50
+ ];
51
+
52
+ // ============================================================================
53
+ // HANDLERS
54
+ // ============================================================================
55
+
56
+ async function handleX402Request(
57
+ args: Record<string, unknown>,
58
+ ): Promise<unknown> {
59
+ if (!hasAuthentication()) {
60
+ return {
61
+ error: "No wallet configured. Call configure_wallet first. x402 requires a signing key for payments.",
62
+ };
63
+ }
64
+
65
+ const sdk = getAuthenticatedSDK();
66
+ if (!sdk) return { error: "Failed to initialize authenticated SDK." };
67
+
68
+ const url = args.url as string;
69
+ const method = ((args.method as string) ?? "GET").toUpperCase();
70
+ const autoPay = (args.autoPay as boolean) ?? false;
71
+ const maxPaymentUsd = (args.maxPaymentUsd as number) ?? 1.0;
72
+ const headers = (args.headers as Record<string, string>) ?? {};
73
+ const body = args.body as string | undefined;
74
+
75
+ const fetchOptions: RequestInit = {
76
+ method,
77
+ headers: { ...headers },
78
+ };
79
+ if (body && (method === "POST" || method === "PUT")) {
80
+ fetchOptions.body = body;
81
+ if (!headers["Content-Type"]) {
82
+ (fetchOptions.headers as Record<string, string>)["Content-Type"] = "application/json";
83
+ }
84
+ }
85
+
86
+ const result = await sdk.request({
87
+ url,
88
+ method: method as "GET" | "POST" | "PUT" | "DELETE",
89
+ headers,
90
+ body: body ? JSON.parse(body) : undefined,
91
+ });
92
+
93
+ if (isX402Required(result)) {
94
+ const payment = result.x402Payment;
95
+
96
+ if (!autoPay) {
97
+ return {
98
+ status: "payment_required",
99
+ x402: true,
100
+ paymentDetails: {
101
+ note: "Server requires x402 payment. Set autoPay: true to pay automatically.",
102
+ },
103
+ };
104
+ }
105
+
106
+ // Auto-pay: execute payment and retry
107
+ try {
108
+ const paidResult = await payment.pay();
109
+ return {
110
+ status: "paid",
111
+ x402: true,
112
+ result: paidResult,
113
+ };
114
+ } catch (payError: unknown) {
115
+ const msg = payError instanceof Error ? payError.message : String(payError);
116
+ return {
117
+ status: "payment_failed",
118
+ x402: true,
119
+ error: msg,
120
+ };
121
+ }
122
+ }
123
+
124
+ // Successful response (no 402)
125
+ return {
126
+ status: "ok",
127
+ x402: false,
128
+ result: result,
129
+ };
130
+ }
131
+
132
+ // ============================================================================
133
+ // DISPATCH
134
+ // ============================================================================
135
+
136
+ export async function handlePaymentsTool(
137
+ name: string,
138
+ args: Record<string, unknown>,
139
+ ): Promise<unknown> {
140
+ switch (name) {
141
+ case "x402_request":
142
+ return handleX402Request(args);
143
+ default:
144
+ return { error: `Unknown payments tool: ${name}` };
145
+ }
146
+ }