@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.
- package/.env.example +17 -0
- package/Dockerfile +25 -0
- package/README.md +85 -0
- package/dist/auth/sdk-client.d.ts +47 -0
- package/dist/auth/sdk-client.js +142 -0
- package/dist/auth/sdk-client.js.map +1 -0
- package/dist/auth/token.d.ts +5 -0
- package/dist/auth/token.js +6 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/auth/wallet-derivation.d.ts +26 -0
- package/dist/auth/wallet-derivation.js +76 -0
- package/dist/auth/wallet-derivation.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +140 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/a2a.d.ts +3 -0
- package/dist/tools/a2a.js +170 -0
- package/dist/tools/a2a.js.map +1 -0
- package/dist/tools/discovery.d.ts +3 -0
- package/dist/tools/discovery.js +465 -0
- package/dist/tools/discovery.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +38 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/payments.d.ts +3 -0
- package/dist/tools/payments.js +124 -0
- package/dist/tools/payments.js.map +1 -0
- package/dist/tools/registration.d.ts +3 -0
- package/dist/tools/registration.js +324 -0
- package/dist/tools/registration.js.map +1 -0
- package/dist/tools/reputation.d.ts +3 -0
- package/dist/tools/reputation.js +147 -0
- package/dist/tools/reputation.js.map +1 -0
- package/dist/utils/chains.d.ts +10 -0
- package/dist/utils/chains.js +33 -0
- package/dist/utils/chains.js.map +1 -0
- package/dist/utils/trust-labels.d.ts +10 -0
- package/dist/utils/trust-labels.js +48 -0
- package/dist/utils/trust-labels.js.map +1 -0
- package/dist/utils/validation.d.ts +12 -0
- package/dist/utils/validation.js +19 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +32 -0
- package/src/auth/sdk-client.ts +171 -0
- package/src/auth/token.ts +19 -0
- package/src/auth/wallet-derivation.ts +91 -0
- package/src/index.ts +184 -0
- package/src/tools/a2a.ts +205 -0
- package/src/tools/discovery.ts +517 -0
- package/src/tools/index.ts +45 -0
- package/src/tools/payments.ts +146 -0
- package/src/tools/registration.ts +389 -0
- package/src/tools/reputation.ts +183 -0
- package/src/utils/chains.ts +42 -0
- package/src/utils/trust-labels.ts +53 -0
- package/src/utils/validation.ts +20 -0
- package/tests/a2a.test.ts +234 -0
- package/tests/chains.test.ts +57 -0
- package/tests/discovery.test.ts +455 -0
- package/tests/e2e.test.ts +234 -0
- package/tests/payments.test.ts +148 -0
- package/tests/registration.test.ts +313 -0
- package/tests/reputation.test.ts +231 -0
- package/tests/sdk-client.test.ts +143 -0
- package/tests/tool-router.test.ts +28 -0
- package/tests/trust-labels.test.ts +229 -0
- package/tests/validation.test.ts +132 -0
- package/tests/wallet-derivation.test.ts +109 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { discoveryTools, handleDiscoveryTool } from "../src/tools/discovery.js";
|
|
3
|
+
|
|
4
|
+
// Mock agent0-sdk so tests don't hit the real API
|
|
5
|
+
vi.mock("agent0-sdk", () => {
|
|
6
|
+
const mockSearchAgents = vi.fn();
|
|
7
|
+
const mockGetAgent = vi.fn();
|
|
8
|
+
const mockGetReputationSummary = vi.fn();
|
|
9
|
+
const mockSearchFeedback = vi.fn();
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
SDK: vi.fn().mockImplementation(() => ({
|
|
13
|
+
searchAgents: mockSearchAgents,
|
|
14
|
+
getAgent: mockGetAgent,
|
|
15
|
+
getReputationSummary: mockGetReputationSummary,
|
|
16
|
+
searchFeedback: mockSearchFeedback,
|
|
17
|
+
})),
|
|
18
|
+
__mocks: {
|
|
19
|
+
mockSearchAgents,
|
|
20
|
+
mockGetAgent,
|
|
21
|
+
mockGetReputationSummary,
|
|
22
|
+
mockSearchFeedback,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Access mock functions
|
|
28
|
+
async function getMocks() {
|
|
29
|
+
const mod = await import("agent0-sdk");
|
|
30
|
+
return (mod as unknown as { __mocks: Record<string, ReturnType<typeof vi.fn>> }).__mocks;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function makeAgent(overrides: Record<string, unknown> = {}) {
|
|
34
|
+
return {
|
|
35
|
+
agentId: "11155111:42",
|
|
36
|
+
chainId: 11155111,
|
|
37
|
+
name: "Test Agent",
|
|
38
|
+
description: "A test agent",
|
|
39
|
+
image: "https://example.com/img.png",
|
|
40
|
+
active: true,
|
|
41
|
+
x402support: false,
|
|
42
|
+
owners: ["0x1234567890abcdef1234567890abcdef12345678"],
|
|
43
|
+
walletAddress: "0x1234567890abcdef1234567890abcdef12345678",
|
|
44
|
+
mcp: null,
|
|
45
|
+
a2a: null,
|
|
46
|
+
web: null,
|
|
47
|
+
ens: null,
|
|
48
|
+
mcpTools: [],
|
|
49
|
+
a2aSkills: [],
|
|
50
|
+
oasfSkills: [],
|
|
51
|
+
oasfDomains: [],
|
|
52
|
+
supportedTrusts: [],
|
|
53
|
+
feedbackCount: 0,
|
|
54
|
+
averageValue: 0,
|
|
55
|
+
updatedAt: 1700000000,
|
|
56
|
+
...overrides,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe("discovery tools", () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
vi.clearAllMocks();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ===========================================================================
|
|
66
|
+
// Tool registration
|
|
67
|
+
// ===========================================================================
|
|
68
|
+
describe("tool registration", () => {
|
|
69
|
+
it("registers 6 discovery tools", () => {
|
|
70
|
+
expect(discoveryTools).toHaveLength(6);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const expectedTools = [
|
|
74
|
+
"search_agents",
|
|
75
|
+
"get_agent",
|
|
76
|
+
"get_supported_chains",
|
|
77
|
+
"get_platform_stats",
|
|
78
|
+
"get_reputation_summary",
|
|
79
|
+
"search_feedback",
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
for (const toolName of expectedTools) {
|
|
83
|
+
it(`registers ${toolName}`, () => {
|
|
84
|
+
const tool = discoveryTools.find((t) => t.name === toolName);
|
|
85
|
+
expect(tool).toBeDefined();
|
|
86
|
+
expect(tool!.description).toBeTypeOf("string");
|
|
87
|
+
expect(tool!.description.length).toBeGreaterThan(10);
|
|
88
|
+
expect(tool!.inputSchema).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ===========================================================================
|
|
94
|
+
// search_agents
|
|
95
|
+
// ===========================================================================
|
|
96
|
+
describe("search_agents", () => {
|
|
97
|
+
it("returns formatted agents with trust labels", async () => {
|
|
98
|
+
const mocks = await getMocks();
|
|
99
|
+
mocks.mockSearchAgents.mockResolvedValue([
|
|
100
|
+
makeAgent({ feedbackCount: 25, averageValue: 90 }),
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const result = (await handleDiscoveryTool("search_agents", {})) as {
|
|
104
|
+
count: number;
|
|
105
|
+
agents: Array<{ trust: string }>;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
expect(result.count).toBe(1);
|
|
109
|
+
expect(result.agents[0].trust).toContain("Highly Trusted");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("respects limit parameter", async () => {
|
|
113
|
+
const mocks = await getMocks();
|
|
114
|
+
const manyAgents = Array.from({ length: 50 }, (_, i) =>
|
|
115
|
+
makeAgent({ agentId: `11155111:${i}` }),
|
|
116
|
+
);
|
|
117
|
+
mocks.mockSearchAgents.mockResolvedValue(manyAgents);
|
|
118
|
+
|
|
119
|
+
const result = (await handleDiscoveryTool("search_agents", {
|
|
120
|
+
limit: 5,
|
|
121
|
+
})) as { count: number; totalAvailable: number };
|
|
122
|
+
|
|
123
|
+
expect(result.count).toBe(5);
|
|
124
|
+
expect(result.totalAvailable).toBe(50);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("caps limit at 100", async () => {
|
|
128
|
+
const mocks = await getMocks();
|
|
129
|
+
const agents = Array.from({ length: 150 }, (_, i) =>
|
|
130
|
+
makeAgent({ agentId: `11155111:${i}` }),
|
|
131
|
+
);
|
|
132
|
+
mocks.mockSearchAgents.mockResolvedValue(agents);
|
|
133
|
+
|
|
134
|
+
const result = (await handleDiscoveryTool("search_agents", {
|
|
135
|
+
limit: 200,
|
|
136
|
+
})) as { count: number };
|
|
137
|
+
|
|
138
|
+
expect(result.count).toBe(100);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("handles empty results", async () => {
|
|
142
|
+
const mocks = await getMocks();
|
|
143
|
+
mocks.mockSearchAgents.mockResolvedValue([]);
|
|
144
|
+
|
|
145
|
+
const result = (await handleDiscoveryTool("search_agents", {
|
|
146
|
+
name: "nonexistent",
|
|
147
|
+
})) as { count: number; agents: unknown[] };
|
|
148
|
+
|
|
149
|
+
expect(result.count).toBe(0);
|
|
150
|
+
expect(result.agents).toEqual([]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("passes filter parameters to SDK", async () => {
|
|
154
|
+
const mocks = await getMocks();
|
|
155
|
+
mocks.mockSearchAgents.mockResolvedValue([]);
|
|
156
|
+
|
|
157
|
+
await handleDiscoveryTool("search_agents", {
|
|
158
|
+
name: "defi",
|
|
159
|
+
active: true,
|
|
160
|
+
x402support: true,
|
|
161
|
+
hasMCP: true,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(mocks.mockSearchAgents).toHaveBeenCalledWith(
|
|
165
|
+
expect.objectContaining({
|
|
166
|
+
name: "defi",
|
|
167
|
+
active: true,
|
|
168
|
+
x402support: true,
|
|
169
|
+
hasMCP: true,
|
|
170
|
+
}),
|
|
171
|
+
expect.any(Object),
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ===========================================================================
|
|
177
|
+
// get_agent
|
|
178
|
+
// ===========================================================================
|
|
179
|
+
describe("get_agent", () => {
|
|
180
|
+
it("returns full agent profile with reputation", async () => {
|
|
181
|
+
const mocks = await getMocks();
|
|
182
|
+
mocks.mockGetAgent.mockResolvedValue(
|
|
183
|
+
makeAgent({ name: "DeFi Bot", feedbackCount: 10, averageValue: 75 }),
|
|
184
|
+
);
|
|
185
|
+
mocks.mockGetReputationSummary.mockResolvedValue({
|
|
186
|
+
count: 10,
|
|
187
|
+
averageValue: 75,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const result = (await handleDiscoveryTool("get_agent", {
|
|
191
|
+
agentId: "11155111:42",
|
|
192
|
+
})) as { name: string; reputation: { trustLabel: string }; chain: string };
|
|
193
|
+
|
|
194
|
+
expect(result.name).toBe("DeFi Bot");
|
|
195
|
+
expect(result.reputation.trustLabel).toBe("Trusted");
|
|
196
|
+
expect(result.chain).toContain("Chain"); // getChainName for 11155111 returns fallback
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("returns error for missing agentId", async () => {
|
|
200
|
+
const result = (await handleDiscoveryTool("get_agent", {})) as {
|
|
201
|
+
error: string;
|
|
202
|
+
};
|
|
203
|
+
expect(result.error).toContain("agentId is required");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("returns error for invalid agentId format", async () => {
|
|
207
|
+
const result = (await handleDiscoveryTool("get_agent", {
|
|
208
|
+
agentId: "invalid",
|
|
209
|
+
})) as { error: string };
|
|
210
|
+
expect(result.error).toContain("Invalid agentId format");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("returns error when agent not found", async () => {
|
|
214
|
+
const mocks = await getMocks();
|
|
215
|
+
mocks.mockGetAgent.mockResolvedValue(null);
|
|
216
|
+
|
|
217
|
+
const result = (await handleDiscoveryTool("get_agent", {
|
|
218
|
+
agentId: "11155111:99999",
|
|
219
|
+
})) as { error: string };
|
|
220
|
+
expect(result.error).toContain("not found");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("handles reputation fetch failure gracefully", async () => {
|
|
224
|
+
const mocks = await getMocks();
|
|
225
|
+
mocks.mockGetAgent.mockResolvedValue(makeAgent());
|
|
226
|
+
mocks.mockGetReputationSummary.mockRejectedValue(
|
|
227
|
+
new Error("Reputation unavailable"),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const result = (await handleDiscoveryTool("get_agent", {
|
|
231
|
+
agentId: "11155111:42",
|
|
232
|
+
})) as { reputation: { count: number; averageValue: number } };
|
|
233
|
+
|
|
234
|
+
// Should still return agent with default reputation
|
|
235
|
+
expect(result.reputation.count).toBe(0);
|
|
236
|
+
expect(result.reputation.averageValue).toBe(0);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ===========================================================================
|
|
241
|
+
// get_supported_chains
|
|
242
|
+
// ===========================================================================
|
|
243
|
+
describe("get_supported_chains", () => {
|
|
244
|
+
it("returns chain list with count", async () => {
|
|
245
|
+
const result = (await handleDiscoveryTool(
|
|
246
|
+
"get_supported_chains",
|
|
247
|
+
{},
|
|
248
|
+
)) as { count: number; chains: Array<{ chainId: number; name: string }> };
|
|
249
|
+
|
|
250
|
+
expect(result.count).toBeGreaterThan(0);
|
|
251
|
+
expect(result.chains.length).toBe(result.count);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("includes Ethereum Mainnet and Base", async () => {
|
|
255
|
+
const result = (await handleDiscoveryTool(
|
|
256
|
+
"get_supported_chains",
|
|
257
|
+
{},
|
|
258
|
+
)) as { chains: Array<{ chainId: number }> };
|
|
259
|
+
|
|
260
|
+
const chainIds = result.chains.map((c) => c.chainId);
|
|
261
|
+
expect(chainIds).toContain(1);
|
|
262
|
+
expect(chainIds).toContain(8453);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("each chain has required fields", async () => {
|
|
266
|
+
const result = (await handleDiscoveryTool(
|
|
267
|
+
"get_supported_chains",
|
|
268
|
+
{},
|
|
269
|
+
)) as {
|
|
270
|
+
chains: Array<{
|
|
271
|
+
chainId: number;
|
|
272
|
+
name: string;
|
|
273
|
+
identity: string;
|
|
274
|
+
reputation: string;
|
|
275
|
+
hasSubgraph: boolean;
|
|
276
|
+
}>;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
for (const chain of result.chains) {
|
|
280
|
+
expect(chain.chainId).toBeTypeOf("number");
|
|
281
|
+
expect(chain.name).toBeTypeOf("string");
|
|
282
|
+
expect(chain.identity).toMatch(/^0x/);
|
|
283
|
+
expect(chain.reputation).toMatch(/^0x/);
|
|
284
|
+
expect(chain.hasSubgraph).toBeTypeOf("boolean");
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// ===========================================================================
|
|
290
|
+
// get_platform_stats
|
|
291
|
+
// ===========================================================================
|
|
292
|
+
describe("get_platform_stats", () => {
|
|
293
|
+
it("returns aggregate stats", async () => {
|
|
294
|
+
const mocks = await getMocks();
|
|
295
|
+
mocks.mockSearchAgents.mockResolvedValue([
|
|
296
|
+
makeAgent({ active: true, mcp: "https://mcp.test", x402support: true }),
|
|
297
|
+
makeAgent({ active: false, a2a: "https://a2a.test", feedbackCount: 5 }),
|
|
298
|
+
makeAgent({ active: true }),
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
const result = (await handleDiscoveryTool("get_platform_stats", {})) as {
|
|
302
|
+
totalAgents: number;
|
|
303
|
+
activeAgents: number;
|
|
304
|
+
agentsWithMCP: number;
|
|
305
|
+
agentsWithA2A: number;
|
|
306
|
+
agentsWithX402: number;
|
|
307
|
+
agentsWithFeedback: number;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
expect(result.totalAgents).toBe(3);
|
|
311
|
+
expect(result.activeAgents).toBe(2);
|
|
312
|
+
expect(result.agentsWithMCP).toBe(1);
|
|
313
|
+
expect(result.agentsWithA2A).toBe(1);
|
|
314
|
+
expect(result.agentsWithX402).toBe(1);
|
|
315
|
+
expect(result.agentsWithFeedback).toBe(1);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("uses provided chainId", async () => {
|
|
319
|
+
const mocks = await getMocks();
|
|
320
|
+
mocks.mockSearchAgents.mockResolvedValue([]);
|
|
321
|
+
const { SDK } = await import("agent0-sdk");
|
|
322
|
+
|
|
323
|
+
await handleDiscoveryTool("get_platform_stats", { chainId: 8453 });
|
|
324
|
+
|
|
325
|
+
expect(SDK).toHaveBeenCalledWith(
|
|
326
|
+
expect.objectContaining({ chainId: 8453 }),
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// ===========================================================================
|
|
332
|
+
// get_reputation_summary
|
|
333
|
+
// ===========================================================================
|
|
334
|
+
describe("get_reputation_summary", () => {
|
|
335
|
+
it("returns reputation with trust label", async () => {
|
|
336
|
+
const mocks = await getMocks();
|
|
337
|
+
mocks.mockGetReputationSummary.mockResolvedValue({
|
|
338
|
+
count: 15,
|
|
339
|
+
averageValue: 72,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const result = (await handleDiscoveryTool("get_reputation_summary", {
|
|
343
|
+
agentId: "11155111:42",
|
|
344
|
+
})) as { trustLabel: string; count: number; averageValue: number };
|
|
345
|
+
|
|
346
|
+
expect(result.count).toBe(15);
|
|
347
|
+
expect(result.averageValue).toBe(72);
|
|
348
|
+
expect(result.trustLabel).toBe("Trusted");
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("returns error for missing agentId", async () => {
|
|
352
|
+
const result = (await handleDiscoveryTool(
|
|
353
|
+
"get_reputation_summary",
|
|
354
|
+
{},
|
|
355
|
+
)) as { error: string };
|
|
356
|
+
expect(result.error).toContain("agentId is required");
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("returns error for invalid format", async () => {
|
|
360
|
+
const result = (await handleDiscoveryTool("get_reputation_summary", {
|
|
361
|
+
agentId: "bad-format",
|
|
362
|
+
})) as { error: string };
|
|
363
|
+
expect(result.error).toContain("Invalid agentId format");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("passes tag to SDK", async () => {
|
|
367
|
+
const mocks = await getMocks();
|
|
368
|
+
mocks.mockGetReputationSummary.mockResolvedValue({
|
|
369
|
+
count: 0,
|
|
370
|
+
averageValue: 0,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
await handleDiscoveryTool("get_reputation_summary", {
|
|
374
|
+
agentId: "11155111:42",
|
|
375
|
+
tag: "quality",
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
expect(mocks.mockGetReputationSummary).toHaveBeenCalledWith(
|
|
379
|
+
"11155111:42",
|
|
380
|
+
"quality",
|
|
381
|
+
);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// ===========================================================================
|
|
386
|
+
// search_feedback
|
|
387
|
+
// ===========================================================================
|
|
388
|
+
describe("search_feedback", () => {
|
|
389
|
+
it("returns formatted feedback entries", async () => {
|
|
390
|
+
const mocks = await getMocks();
|
|
391
|
+
mocks.mockSearchFeedback.mockResolvedValue([
|
|
392
|
+
{
|
|
393
|
+
agentId: "11155111:42",
|
|
394
|
+
reviewer: "0xabc",
|
|
395
|
+
value: 85,
|
|
396
|
+
tags: ["quality"],
|
|
397
|
+
text: "Great agent",
|
|
398
|
+
endpoint: "mcp",
|
|
399
|
+
isRevoked: false,
|
|
400
|
+
createdAt: 1700000000,
|
|
401
|
+
mcpTool: "search",
|
|
402
|
+
a2aSkills: [],
|
|
403
|
+
},
|
|
404
|
+
]);
|
|
405
|
+
|
|
406
|
+
const result = (await handleDiscoveryTool("search_feedback", {
|
|
407
|
+
agentId: "11155111:42",
|
|
408
|
+
})) as { count: number; feedbacks: Array<{ value: number }> };
|
|
409
|
+
|
|
410
|
+
expect(result.count).toBe(1);
|
|
411
|
+
expect(result.feedbacks[0].value).toBe(85);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("returns error when no agent specified", async () => {
|
|
415
|
+
const result = (await handleDiscoveryTool("search_feedback", {})) as {
|
|
416
|
+
error: string;
|
|
417
|
+
};
|
|
418
|
+
expect(result.error).toContain("Provide agentId or agents[]");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("caps results at 50", async () => {
|
|
422
|
+
const mocks = await getMocks();
|
|
423
|
+
const manyFeedbacks = Array.from({ length: 60 }, (_, i) => ({
|
|
424
|
+
agentId: "11155111:42",
|
|
425
|
+
reviewer: `0x${i}`,
|
|
426
|
+
value: 50,
|
|
427
|
+
tags: [],
|
|
428
|
+
text: "",
|
|
429
|
+
endpoint: null,
|
|
430
|
+
isRevoked: false,
|
|
431
|
+
createdAt: 1700000000,
|
|
432
|
+
mcpTool: null,
|
|
433
|
+
a2aSkills: [],
|
|
434
|
+
}));
|
|
435
|
+
mocks.mockSearchFeedback.mockResolvedValue(manyFeedbacks);
|
|
436
|
+
|
|
437
|
+
const result = (await handleDiscoveryTool("search_feedback", {
|
|
438
|
+
agentId: "11155111:42",
|
|
439
|
+
})) as { count: number; feedbacks: unknown[] };
|
|
440
|
+
|
|
441
|
+
expect(result.count).toBe(60);
|
|
442
|
+
expect(result.feedbacks).toHaveLength(50);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// ===========================================================================
|
|
447
|
+
// Unknown tool
|
|
448
|
+
// ===========================================================================
|
|
449
|
+
it("returns error for unknown tool name", async () => {
|
|
450
|
+
const result = (await handleDiscoveryTool("nonexistent", {})) as {
|
|
451
|
+
error: string;
|
|
452
|
+
};
|
|
453
|
+
expect(result.error).toContain("Unknown discovery tool");
|
|
454
|
+
});
|
|
455
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests against live 8004scan / agent0-sdk API.
|
|
3
|
+
*
|
|
4
|
+
* Run with: LIVE_TESTS=1 npx vitest run tests/e2e.test.ts
|
|
5
|
+
*
|
|
6
|
+
* These tests call the real agent0-sdk which hits the 8004scan subgraph.
|
|
7
|
+
* They are slow (~5-15s each) and require network access.
|
|
8
|
+
* Set EIGHTSCAN_API_KEY to avoid rate limits.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, it, expect } from "vitest";
|
|
11
|
+
import { handleDiscoveryTool } from "../src/tools/discovery.js";
|
|
12
|
+
|
|
13
|
+
const LIVE = !!process.env.LIVE_TESTS;
|
|
14
|
+
|
|
15
|
+
describe.skipIf(!LIVE)("e2e: live agent0-sdk via discovery tools", () => {
|
|
16
|
+
it(
|
|
17
|
+
"search_agents: returns agents on default chain (Sepolia)",
|
|
18
|
+
async () => {
|
|
19
|
+
const result = (await handleDiscoveryTool("search_agents", {
|
|
20
|
+
limit: 5,
|
|
21
|
+
})) as {
|
|
22
|
+
count: number;
|
|
23
|
+
totalAvailable: number;
|
|
24
|
+
chainId: number;
|
|
25
|
+
agents: Array<{
|
|
26
|
+
agentId: string;
|
|
27
|
+
name: string;
|
|
28
|
+
trust: string;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
expect(result.count).toBeGreaterThan(0);
|
|
33
|
+
expect(result.count).toBeLessThanOrEqual(5);
|
|
34
|
+
expect(result.totalAvailable).toBeGreaterThan(0);
|
|
35
|
+
|
|
36
|
+
// Each agent should have required fields
|
|
37
|
+
for (const agent of result.agents) {
|
|
38
|
+
expect(agent.agentId).toBeTypeOf("string");
|
|
39
|
+
expect(agent.agentId).toContain(":");
|
|
40
|
+
expect(agent.name).toBeTypeOf("string");
|
|
41
|
+
expect(agent.trust).toBeTypeOf("string");
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
30_000,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
it(
|
|
48
|
+
"search_agents: name filter returns matching results",
|
|
49
|
+
async () => {
|
|
50
|
+
const result = (await handleDiscoveryTool("search_agents", {
|
|
51
|
+
name: "test",
|
|
52
|
+
limit: 10,
|
|
53
|
+
})) as {
|
|
54
|
+
count: number;
|
|
55
|
+
agents: Array<{ name: string }>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// May return 0 if no agents match, but should not error
|
|
59
|
+
expect(result.count).toBeGreaterThanOrEqual(0);
|
|
60
|
+
expect(Array.isArray(result.agents)).toBe(true);
|
|
61
|
+
},
|
|
62
|
+
30_000,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
it(
|
|
66
|
+
"get_agent: returns a known agent profile",
|
|
67
|
+
async () => {
|
|
68
|
+
// First search to find a real agent ID
|
|
69
|
+
const searchResult = (await handleDiscoveryTool("search_agents", {
|
|
70
|
+
limit: 1,
|
|
71
|
+
})) as {
|
|
72
|
+
agents: Array<{ agentId: string }>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
expect(searchResult.agents.length).toBeGreaterThan(0);
|
|
76
|
+
const agentId = searchResult.agents[0].agentId;
|
|
77
|
+
|
|
78
|
+
const result = (await handleDiscoveryTool("get_agent", {
|
|
79
|
+
agentId,
|
|
80
|
+
})) as {
|
|
81
|
+
agentId: string;
|
|
82
|
+
name: string;
|
|
83
|
+
reputation: {
|
|
84
|
+
count: number;
|
|
85
|
+
averageValue: number;
|
|
86
|
+
trustLabel: string;
|
|
87
|
+
trustDisplay: string;
|
|
88
|
+
};
|
|
89
|
+
chain: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
expect(result.agentId).toBe(agentId);
|
|
93
|
+
expect(result.name).toBeTypeOf("string");
|
|
94
|
+
expect(result.reputation).toBeDefined();
|
|
95
|
+
expect(result.reputation.trustLabel).toBeTypeOf("string");
|
|
96
|
+
expect(result.chain).toBeTypeOf("string");
|
|
97
|
+
},
|
|
98
|
+
30_000,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
it(
|
|
102
|
+
"get_agent: returns error for nonexistent agent",
|
|
103
|
+
async () => {
|
|
104
|
+
const result = (await handleDiscoveryTool("get_agent", {
|
|
105
|
+
agentId: "11155111:999999999",
|
|
106
|
+
})) as { error?: string };
|
|
107
|
+
|
|
108
|
+
expect(result.error).toBeDefined();
|
|
109
|
+
expect(result.error).toContain("not found");
|
|
110
|
+
},
|
|
111
|
+
30_000,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
it(
|
|
115
|
+
"get_supported_chains: returns chain list with contract addresses",
|
|
116
|
+
async () => {
|
|
117
|
+
const result = (await handleDiscoveryTool(
|
|
118
|
+
"get_supported_chains",
|
|
119
|
+
{},
|
|
120
|
+
)) as {
|
|
121
|
+
count: number;
|
|
122
|
+
chains: Array<{
|
|
123
|
+
chainId: number;
|
|
124
|
+
name: string;
|
|
125
|
+
identity: string;
|
|
126
|
+
reputation: string;
|
|
127
|
+
hasSubgraph: boolean;
|
|
128
|
+
}>;
|
|
129
|
+
note: string;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
expect(result.count).toBeGreaterThanOrEqual(3);
|
|
133
|
+
expect(result.chains.length).toBe(result.count);
|
|
134
|
+
|
|
135
|
+
// Verify known chains are present
|
|
136
|
+
const chainIds = result.chains.map((c) => c.chainId);
|
|
137
|
+
expect(chainIds).toContain(1); // Ethereum
|
|
138
|
+
expect(chainIds).toContain(8453); // Base
|
|
139
|
+
expect(chainIds).toContain(11155111); // Sepolia
|
|
140
|
+
|
|
141
|
+
// Each chain has contract addresses
|
|
142
|
+
for (const chain of result.chains) {
|
|
143
|
+
expect(chain.identity).toMatch(/^0x[0-9a-fA-F]{40}$/);
|
|
144
|
+
expect(chain.reputation).toMatch(/^0x[0-9a-fA-F]{40}$/);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
15_000,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
it(
|
|
151
|
+
"get_platform_stats: returns aggregate stats for Sepolia",
|
|
152
|
+
async () => {
|
|
153
|
+
const result = (await handleDiscoveryTool("get_platform_stats", {
|
|
154
|
+
chainId: 11155111,
|
|
155
|
+
})) as {
|
|
156
|
+
chainId: number;
|
|
157
|
+
chain: string;
|
|
158
|
+
totalAgents: number;
|
|
159
|
+
activeAgents: number;
|
|
160
|
+
agentsWithMCP: number;
|
|
161
|
+
agentsWithA2A: number;
|
|
162
|
+
agentsWithX402: number;
|
|
163
|
+
totalMCPTools: number;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
expect(result.chainId).toBe(11155111);
|
|
167
|
+
expect(result.totalAgents).toBeGreaterThan(0);
|
|
168
|
+
expect(result.activeAgents).toBeGreaterThanOrEqual(0);
|
|
169
|
+
expect(result.activeAgents).toBeLessThanOrEqual(result.totalAgents);
|
|
170
|
+
},
|
|
171
|
+
30_000,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
it(
|
|
175
|
+
"get_reputation_summary: returns trust label for an agent",
|
|
176
|
+
async () => {
|
|
177
|
+
// Find a real agent first
|
|
178
|
+
const searchResult = (await handleDiscoveryTool("search_agents", {
|
|
179
|
+
limit: 1,
|
|
180
|
+
})) as { agents: Array<{ agentId: string }> };
|
|
181
|
+
const agentId = searchResult.agents[0].agentId;
|
|
182
|
+
|
|
183
|
+
const result = (await handleDiscoveryTool("get_reputation_summary", {
|
|
184
|
+
agentId,
|
|
185
|
+
})) as {
|
|
186
|
+
agentId: string;
|
|
187
|
+
count: number;
|
|
188
|
+
averageValue: number;
|
|
189
|
+
trustLabel: string;
|
|
190
|
+
trustEmoji: string;
|
|
191
|
+
trustDisplay: string;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
expect(result.agentId).toBe(agentId);
|
|
195
|
+
expect(result.count).toBeGreaterThanOrEqual(0);
|
|
196
|
+
expect(result.trustLabel).toBeTypeOf("string");
|
|
197
|
+
expect(result.trustEmoji).toBeTypeOf("string");
|
|
198
|
+
},
|
|
199
|
+
30_000,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
it(
|
|
203
|
+
"search_feedback: returns feedback entries (may be empty)",
|
|
204
|
+
async () => {
|
|
205
|
+
// Find a real agent first
|
|
206
|
+
const searchResult = (await handleDiscoveryTool("search_agents", {
|
|
207
|
+
limit: 1,
|
|
208
|
+
})) as { agents: Array<{ agentId: string }> };
|
|
209
|
+
const agentId = searchResult.agents[0].agentId;
|
|
210
|
+
|
|
211
|
+
const result = (await handleDiscoveryTool("search_feedback", {
|
|
212
|
+
agentId,
|
|
213
|
+
})) as {
|
|
214
|
+
count: number;
|
|
215
|
+
feedbacks: Array<{
|
|
216
|
+
agentId: string;
|
|
217
|
+
reviewer: string;
|
|
218
|
+
value: number;
|
|
219
|
+
}>;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
expect(result.count).toBeGreaterThanOrEqual(0);
|
|
223
|
+
expect(Array.isArray(result.feedbacks)).toBe(true);
|
|
224
|
+
|
|
225
|
+
// If any feedback exists, verify shape
|
|
226
|
+
if (result.feedbacks.length > 0) {
|
|
227
|
+
const fb = result.feedbacks[0];
|
|
228
|
+
expect(fb.agentId).toBeTypeOf("string");
|
|
229
|
+
expect(fb.value).toBeTypeOf("number");
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
30_000,
|
|
233
|
+
);
|
|
234
|
+
});
|