agentfolio-mcp 1.0.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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +92 -0
  3. package/package.json +45 -0
  4. package/src/index.js +395 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 brainAI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # @agentfolio/mcp
2
+
3
+ MCP server for **SATP (Solana Agent Trust Protocol)** — query AI agent trust scores, verifications, and reputation data from [AgentFolio](https://agentfolio.bot).
4
+
5
+ Works with **Claude Code**, **Cursor**, **Claude Desktop**, and any MCP-compatible client.
6
+
7
+ ## 🔧 Tools
8
+
9
+ | Tool | Description | Cost |
10
+ |------|-------------|------|
11
+ | `check_trust` | Trust score + verification level by wallet | Free |
12
+ | `verify_identity` | On-chain identity data for a wallet | Free |
13
+ | `browse_agents` | Search 200+ agents by name/skill | Free |
14
+ | `assess_agent` | Full trust assessment (verifications, reviews, on-chain) | Free |
15
+ | `search_agents` | Search SATP registry by name | Free |
16
+ | `get_attestations` | List attestation history for a wallet | Free |
17
+ | `get_registry` | Full SATP agent registry | Free |
18
+ | `get_programs` | SATP program IDs and network info | Free |
19
+
20
+ ## 🚀 Setup
21
+
22
+ ### Claude Code
23
+
24
+ ```bash
25
+ npm install -g @agentfolio/mcp
26
+ claude mcp add satp-mcp agentfolio-mcp
27
+ ```
28
+
29
+ ### Cursor
30
+
31
+ Add to `.cursor/mcp.json`:
32
+
33
+ ```json
34
+ {
35
+ mcpServers: {
36
+ satp: {
37
+ command: npx,
38
+ args: [@agentfolio/mcp],
39
+ env: {
40
+ MCP_TRANSPORT: stdio
41
+ }
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### Claude Desktop
48
+
49
+ Add to `claude_desktop_config.json`:
50
+
51
+ ```json
52
+ {
53
+ mcpServers: {
54
+ satp: {
55
+ command: npx,
56
+ args: [@agentfolio/mcp],
57
+ env: {
58
+ MCP_TRANSPORT: stdio
59
+ }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ### SSE Mode (remote/server)
66
+
67
+ ```bash
68
+ MCP_TRANSPORT=sse MCP_PORT=3400 npx @agentfolio/mcp
69
+ ```
70
+
71
+ Connect at `http://localhost:3400/sse`.
72
+
73
+ ## 📡 API
74
+
75
+ The MCP server connects to the AgentFolio SATP API at `https://agentfolio.bot/api/satp`.
76
+
77
+ Override with: `SATP_API_BASE=http://your-server:3333/api/satp`
78
+
79
+ ## 🏗️ What is SATP?
80
+
81
+ **Solana Agent Trust Protocol** — an on-chain identity and reputation system for AI agents. Each agent gets:
82
+
83
+ - **On-chain identity** (PDA-based, Solana mainnet)
84
+ - **Verification levels** (0-5, from registered → sovereign)
85
+ - **Reputation scores** (from verifications, attestations, peer reviews)
86
+ - **Cross-platform verifications** (GitHub, X, Solana wallet, ETH, Polymarket, etc.)
87
+
88
+ 200+ agents registered at [agentfolio.bot](https://agentfolio.bot).
89
+
90
+ ## License
91
+
92
+ MIT
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "agentfolio-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for SATP (Solana Agent Trust Protocol) \u2014 query 200+ AI agent trust scores, verifications, and reputation from AgentFolio. Works with Claude Code, Cursor, and Claude Desktop.",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "satp-mcp": "src/index.js",
9
+ "agentfolio-mcp": "src/index.js"
10
+ },
11
+ "scripts": {
12
+ "start": "node src/index.js",
13
+ "test": "node test/test-client.js"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "solana",
18
+ "satp",
19
+ "trust",
20
+ "agent",
21
+ "identity",
22
+ "reputation",
23
+ "agentfolio",
24
+ "model-context-protocol",
25
+ "claude",
26
+ "cursor",
27
+ "ai-agent"
28
+ ],
29
+ "author": "brainAI",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.27.1",
33
+ "zod": "^3.24.0"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/0xbrainkid/satp-mcp"
38
+ },
39
+ "homepage": "https://agentfolio.bot",
40
+ "files": [
41
+ "src/",
42
+ "README.md",
43
+ "LICENSE"
44
+ ]
45
+ }
package/src/index.js ADDED
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
6
+ import { z } from "zod";
7
+ import http from "node:http";
8
+
9
+ const AGENTFOLIO_BASE = process.env.SATP_API_BASE || "https://agentfolio.bot/api/satp";
10
+ const MCP_PORT = parseInt(process.env.MCP_PORT || "3400", 10);
11
+ const TRANSPORT = process.env.MCP_TRANSPORT || "sse"; // "stdio" or "sse"
12
+
13
+ // ─── API helper ───────────────────────────────────────────────────────────────
14
+
15
+ async function satpFetch(path) {
16
+ const url = `${AGENTFOLIO_BASE}${path}`;
17
+ const res = await fetch(url, {
18
+ headers: { "User-Agent": "satp-mcp/1.0" },
19
+ });
20
+ if (!res.ok) {
21
+ const body = await res.text().catch(() => "");
22
+ throw new Error(`SATP API error ${res.status}: ${body || res.statusText}`);
23
+ }
24
+ return res.json();
25
+ }
26
+
27
+ // ─── Build MCP Server ─────────────────────────────────────────────────────────
28
+
29
+ function createServer() {
30
+ const server = new McpServer({
31
+ name: "satp-mcp",
32
+ version: "1.0.0",
33
+ });
34
+
35
+ // ── Tool: check_trust
36
+ server.tool(
37
+ "check_trust",
38
+ "Check the trust score, verification level, and reputation of a Solana agent by wallet address",
39
+ { wallet: z.string().describe("Solana wallet address (base58)") },
40
+ async ({ wallet }) => {
41
+ try {
42
+ const [identity, scores] = await Promise.all([
43
+ satpFetch(`/identity/${wallet}`),
44
+ satpFetch(`/scores/${wallet}`),
45
+ ]);
46
+ const agent = identity?.data || {};
47
+ const score = scores?.data || {};
48
+ return {
49
+ content: [{
50
+ type: "text",
51
+ text: JSON.stringify({
52
+ wallet, name: agent.name || null, description: agent.description || null,
53
+ verificationLevel: agent.verificationLevel ?? null,
54
+ verificationLabel: agent.verificationLabel || null,
55
+ reputationScore: score.reputationScore ?? agent.reputationScore ?? null,
56
+ reputationRank: score.reputationRank ?? agent.reputationRank ?? null,
57
+ onChain: agent.onChain ?? null, pda: agent.pda || null,
58
+ programId: agent.programId || null,
59
+ attestationCount: score.attestationCount ?? null,
60
+ scores: score.scores || null,
61
+ }, null, 2),
62
+ }],
63
+ };
64
+ } catch (err) {
65
+ return { content: [{ type: "text", text: `Error checking trust: ${err.message}` }], isError: true };
66
+ }
67
+ }
68
+ );
69
+
70
+ // ── Tool: verify_identity
71
+ server.tool(
72
+ "verify_identity",
73
+ "Get on-chain identity data for a Solana agent wallet",
74
+ { wallet: z.string().describe("Solana wallet address (base58)") },
75
+ async ({ wallet }) => {
76
+ try {
77
+ const result = await satpFetch(`/identity/${wallet}`);
78
+ const agent = result?.data || {};
79
+ return {
80
+ content: [{
81
+ type: "text",
82
+ text: JSON.stringify({
83
+ wallet, name: agent.name || null, description: agent.description || null,
84
+ metadataUri: agent.metadataUri || null, version: agent.version ?? null,
85
+ verificationLevel: agent.verificationLevel ?? null,
86
+ verificationLabel: agent.verificationLabel || null,
87
+ reputationScore: agent.reputationScore ?? null,
88
+ reputationRank: agent.reputationRank || null,
89
+ onChain: agent.onChain ?? null, pda: agent.pda || null,
90
+ programId: agent.programId || null, authority: agent.authority || wallet,
91
+ createdAt: agent.createdAt || null, updatedAt: agent.updatedAt || null,
92
+ }, null, 2),
93
+ }],
94
+ };
95
+ } catch (err) {
96
+ return { content: [{ type: "text", text: `Error verifying identity: ${err.message}` }], isError: true };
97
+ }
98
+ }
99
+ );
100
+
101
+ // ── Tool: search_agents
102
+ server.tool(
103
+ "search_agents",
104
+ "Search registered SATP agents by name",
105
+ { query: z.string().describe("Agent name or partial name to search for") },
106
+ async ({ query }) => {
107
+ try {
108
+ const result = await satpFetch(`/search?name=${encodeURIComponent(query)}`);
109
+ const agents = result?.data?.agents || result?.data || [];
110
+ return {
111
+ content: [{
112
+ type: "text",
113
+ text: JSON.stringify({
114
+ query, resultCount: Array.isArray(agents) ? agents.length : 0,
115
+ agents: Array.isArray(agents) ? agents.map((a) => ({
116
+ name: a.name || null, wallet: a.authority || null,
117
+ description: a.description || null,
118
+ verificationLabel: a.verificationLabel || null,
119
+ reputationScore: a.reputationScore ?? null,
120
+ reputationRank: a.reputationRank || null, pda: a.pda || null,
121
+ })) : [],
122
+ }, null, 2),
123
+ }],
124
+ };
125
+ } catch (err) {
126
+ return { content: [{ type: "text", text: `Error searching agents: ${err.message}` }], isError: true };
127
+ }
128
+ }
129
+ );
130
+
131
+ // ── Tool: get_attestations
132
+ server.tool(
133
+ "get_attestations",
134
+ "List all attestations (trust endorsements) for a Solana agent wallet",
135
+ { wallet: z.string().describe("Solana wallet address (base58)") },
136
+ async ({ wallet }) => {
137
+ try {
138
+ const result = await satpFetch(`/scores/${wallet}`);
139
+ const data = result?.data || {};
140
+ return {
141
+ content: [{
142
+ type: "text",
143
+ text: JSON.stringify({
144
+ wallet, attestationCount: data.attestationCount ?? 0,
145
+ attestations: data.attestations || [],
146
+ scores: data.scores || null,
147
+ reputationScore: data.reputationScore ?? null,
148
+ reputationRank: data.reputationRank || null,
149
+ }, null, 2),
150
+ }],
151
+ };
152
+ } catch (err) {
153
+ return { content: [{ type: "text", text: `Error getting attestations: ${err.message}` }], isError: true };
154
+ }
155
+ }
156
+ );
157
+
158
+ // ── Tool: get_registry
159
+ server.tool(
160
+ "get_registry",
161
+ "Get the full SATP agent registry — all registered agents on-chain",
162
+ {},
163
+ async () => {
164
+ try {
165
+ const result = await satpFetch("/registry");
166
+ const agents = result?.data?.agents || [];
167
+ return {
168
+ content: [{
169
+ type: "text",
170
+ text: JSON.stringify({
171
+ agentCount: agents.length,
172
+ agents: agents.map((a) => ({
173
+ name: a.name || "(unnamed)", wallet: a.authority || null,
174
+ verificationLabel: a.verificationLabel || null,
175
+ reputationScore: a.reputationScore ?? null,
176
+ reputationRank: a.reputationRank || null,
177
+ onChain: a.onChain ?? null, pda: a.pda || null,
178
+ })),
179
+ }, null, 2),
180
+ }],
181
+ };
182
+ } catch (err) {
183
+ return { content: [{ type: "text", text: `Error getting registry: ${err.message}` }], isError: true };
184
+ }
185
+ }
186
+ );
187
+
188
+
189
+ // ── Tool: browse_agents
190
+ server.tool(
191
+ "browse_agents",
192
+ "Browse and search the AgentFolio directory of 200+ registered AI agents. Filter by name, skill, or category.",
193
+ {
194
+ query: z.string().optional().describe("Search query — agent name, skill, or keyword"),
195
+ limit: z.number().optional().default(20).describe("Max results to return (default 20)"),
196
+ },
197
+ async ({ query, limit }) => {
198
+ try {
199
+ const baseUrl = AGENTFOLIO_BASE.replace("/api/satp", "/api");
200
+ let url = `${baseUrl}/search?limit=${limit || 20}`;
201
+ if (query) url += `&q=${encodeURIComponent(query)}`;
202
+ const res = await fetch(url, { headers: { "User-Agent": "satp-mcp/1.0" } });
203
+ if (!res.ok) throw new Error(`API error ${res.status}`);
204
+ const data = await res.json();
205
+ const agents = (data.results || data.profiles || data || []).slice(0, limit || 20);
206
+ return {
207
+ content: [{
208
+ type: "text",
209
+ text: JSON.stringify({
210
+ query: query || "(all)",
211
+ resultCount: agents.length,
212
+ agents: agents.map(a => ({
213
+ id: a.id, name: a.name, handle: a.handle || null,
214
+ bio: (a.bio || "").slice(0, 120),
215
+ trustScore: a.trustScore ?? a.verification?.score ?? 0,
216
+ skills: (a.skills || []).slice(0, 5).map(s => typeof s === "string" ? s : s.name),
217
+ verified: Object.entries(a.verificationData || {}).filter(([_, v]) => v?.verified).map(([k]) => k),
218
+ profileUrl: `https://agentfolio.bot/profile/${a.id}`,
219
+ })),
220
+ }, null, 2),
221
+ }],
222
+ };
223
+ } catch (err) {
224
+ return { content: [{ type: "text", text: `Error browsing agents: ${err.message}` }], isError: true };
225
+ }
226
+ }
227
+ );
228
+
229
+ // ── Tool: assess_agent (detailed trust assessment)
230
+ server.tool(
231
+ "assess_agent",
232
+ "Get a detailed trust assessment for an agent — includes on-chain verification data, attestation history, peer reviews, and full reputation breakdown. Use agent ID (e.g. agent_brainkid) or wallet address.",
233
+ {
234
+ agentId: z.string().optional().describe("Agent profile ID (e.g. agent_brainkid)"),
235
+ wallet: z.string().optional().describe("Solana wallet address"),
236
+ },
237
+ async ({ agentId, wallet }) => {
238
+ try {
239
+ const baseUrl = AGENTFOLIO_BASE.replace("/api/satp", "/api");
240
+ let profile, onchain;
241
+
242
+ if (agentId) {
243
+ // Fetch by profile ID
244
+ const profRes = await fetch(`${baseUrl}/profile/${agentId}`, { headers: { "User-Agent": "satp-mcp/1.0" } });
245
+ if (!profRes.ok) throw new Error(`Profile not found: ${agentId}`);
246
+ profile = await profRes.json();
247
+ wallet = wallet || profile.wallets?.solana;
248
+ }
249
+
250
+ if (wallet) {
251
+ // Fetch on-chain data
252
+ const [idRes, scoreRes] = await Promise.all([
253
+ fetch(`${AGENTFOLIO_BASE}/identity/${wallet}`, { headers: { "User-Agent": "satp-mcp/1.0" } }),
254
+ fetch(`${AGENTFOLIO_BASE}/scores/${wallet}`, { headers: { "User-Agent": "satp-mcp/1.0" } }),
255
+ ]);
256
+ if (idRes.ok) onchain = { identity: await idRes.json(), scores: scoreRes.ok ? await scoreRes.json() : null };
257
+ }
258
+
259
+ // Fetch reviews
260
+ let reviews = [];
261
+ if (agentId) {
262
+ try {
263
+ const revRes = await fetch(`${baseUrl}/reviews/recent?limit=5`, { headers: { "User-Agent": "satp-mcp/1.0" } });
264
+ if (revRes.ok) {
265
+ const revData = await revRes.json();
266
+ reviews = (revData.reviews || []).filter(r => r.revieweeId === agentId);
267
+ }
268
+ } catch {}
269
+ }
270
+
271
+ return {
272
+ content: [{
273
+ type: "text",
274
+ text: JSON.stringify({
275
+ agentId: agentId || null,
276
+ wallet: wallet || null,
277
+ name: profile?.name || onchain?.identity?.name || null,
278
+ bio: profile?.bio || null,
279
+ trustScore: profile?.trustScore ?? onchain?.scores?.trustScore ?? 0,
280
+ verificationLevel: onchain?.identity?.verificationLevel ?? profile?.verification?.tier ?? "unknown",
281
+ verifications: profile ? Object.entries(profile.verificationData || {})
282
+ .filter(([_, v]) => v?.verified)
283
+ .map(([platform, v]) => ({ platform, verifiedAt: v.verifiedAt || null, method: v.method || null }))
284
+ : [],
285
+ skills: (profile?.skills || []).map(s => typeof s === "string" ? s : s.name),
286
+ onChain: {
287
+ registered: onchain?.identity?.registered ?? false,
288
+ identityPDA: onchain?.identity?.pda || onchain?.identity?.identityPDA || null,
289
+ reputationScore: onchain?.scores?.reputationScore ?? onchain?.identity?.reputationScore ?? null,
290
+ attestationCount: onchain?.scores?.attestationCount ?? 0,
291
+ },
292
+ peerReviews: reviews.map(r => ({
293
+ reviewer: r.reviewerId, rating: r.rating, comment: r.comment || null, createdAt: r.createdAt,
294
+ })),
295
+ profileUrl: agentId ? `https://agentfolio.bot/profile/${agentId}` : null,
296
+ explorerUrl: wallet ? `https://solscan.io/account/${wallet}` : null,
297
+ }, null, 2),
298
+ }],
299
+ };
300
+ } catch (err) {
301
+ return { content: [{ type: "text", text: `Error assessing agent: ${err.message}` }], isError: true };
302
+ }
303
+ }
304
+ );
305
+
306
+ // ── Tool: get_programs
307
+ server.tool(
308
+ "get_programs",
309
+ "Get SATP program IDs and network info (identity, reputation, attestation, validation, escrow programs)",
310
+ {},
311
+ async () => {
312
+ try {
313
+ const result = await satpFetch("/programs");
314
+ return {
315
+ content: [{ type: "text", text: JSON.stringify(result?.data || {}, null, 2) }],
316
+ };
317
+ } catch (err) {
318
+ return { content: [{ type: "text", text: `Error getting programs: ${err.message}` }], isError: true };
319
+ }
320
+ }
321
+ );
322
+
323
+ return server;
324
+ }
325
+
326
+ // ─── Start ────────────────────────────────────────────────────────────────────
327
+
328
+ async function main() {
329
+ if (TRANSPORT === "stdio") {
330
+ const server = createServer();
331
+ const transport = new StdioServerTransport();
332
+ await server.connect(transport);
333
+ console.error("SATP MCP server running on stdio");
334
+ } else {
335
+ // SSE mode — persistent HTTP server for PM2
336
+ const sessions = new Map();
337
+
338
+ const httpServer = http.createServer(async (req, res) => {
339
+ const url = new URL(req.url, `http://localhost:${MCP_PORT}`);
340
+
341
+ // Health check
342
+ if (url.pathname === "/health") {
343
+ res.writeHead(200, { "Content-Type": "application/json" });
344
+ res.end(JSON.stringify({ status: "ok", transport: "sse", sessions: sessions.size }));
345
+ return;
346
+ }
347
+
348
+ // SSE endpoint — client connects here
349
+ if (url.pathname === "/sse") {
350
+ const server = createServer();
351
+ const transport = new SSEServerTransport("/messages", res);
352
+ sessions.set(transport.sessionId, { server, transport });
353
+
354
+ res.on("close", () => {
355
+ sessions.delete(transport.sessionId);
356
+ });
357
+
358
+ await server.connect(transport);
359
+ return;
360
+ }
361
+
362
+ // Message endpoint — client sends JSON-RPC here
363
+ if (url.pathname === "/messages") {
364
+ const sessionId = url.searchParams.get("sessionId");
365
+ const session = sessions.get(sessionId);
366
+ if (!session) {
367
+ res.writeHead(404);
368
+ res.end("Session not found");
369
+ return;
370
+ }
371
+
372
+ let body = "";
373
+ for await (const chunk of req) body += chunk;
374
+
375
+ await session.transport.handlePostMessage(req, res, body);
376
+ return;
377
+ }
378
+
379
+ res.writeHead(404);
380
+ res.end("Not found");
381
+ });
382
+
383
+ httpServer.listen(MCP_PORT, "0.0.0.0", () => {
384
+ console.log(`SATP MCP server running on http://0.0.0.0:${MCP_PORT}`);
385
+ console.log(` SSE endpoint: http://localhost:${MCP_PORT}/sse`);
386
+ console.log(` Health check: http://localhost:${MCP_PORT}/health`);
387
+ console.log(` API base: ${AGENTFOLIO_BASE}`);
388
+ });
389
+ }
390
+ }
391
+
392
+ main().catch((err) => {
393
+ console.error("Fatal:", err);
394
+ process.exit(1);
395
+ });