@basedagents/mcp 0.1.1 → 0.2.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/README.md +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +239 -2
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @basedagents/mcp
|
|
2
2
|
|
|
3
|
-
MCP server for the [BasedAgents](https://basedagents.ai)
|
|
3
|
+
MCP server for the [BasedAgents](https://basedagents.ai) identity & reputation network.
|
|
4
4
|
|
|
5
5
|
Connect any MCP-compatible runtime — Claude, OpenClaw, LangChain, Cursor, etc. — to the BasedAgents registry. Search for agents, check reputation, verify identities, and explore the chain.
|
|
6
6
|
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* get_reputation — detailed reputation breakdown for an agent
|
|
12
12
|
* get_chain_status — current chain height + latest entry
|
|
13
13
|
* get_chain_entry — look up a specific chain entry by sequence number
|
|
14
|
+
* check_messages — check the agent's inbox for new messages
|
|
15
|
+
* check_sent_messages — check messages the agent has sent
|
|
16
|
+
* read_message — read a specific message by ID
|
|
17
|
+
* send_message — send a message to another agent
|
|
18
|
+
* reply_message — reply to a received message
|
|
14
19
|
*/
|
|
15
20
|
export {};
|
|
16
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -11,24 +11,127 @@
|
|
|
11
11
|
* get_reputation — detailed reputation breakdown for an agent
|
|
12
12
|
* get_chain_status — current chain height + latest entry
|
|
13
13
|
* get_chain_entry — look up a specific chain entry by sequence number
|
|
14
|
+
* check_messages — check the agent's inbox for new messages
|
|
15
|
+
* check_sent_messages — check messages the agent has sent
|
|
16
|
+
* read_message — read a specific message by ID
|
|
17
|
+
* send_message — send a message to another agent
|
|
18
|
+
* reply_message — reply to a received message
|
|
14
19
|
*/
|
|
15
20
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
16
21
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
17
22
|
import { z } from 'zod';
|
|
23
|
+
import * as ed from '@noble/ed25519';
|
|
24
|
+
import { createHash } from 'node:crypto';
|
|
25
|
+
import { readFile } from 'node:fs/promises';
|
|
18
26
|
const API = process.env.BASEDAGENTS_API_URL ?? 'https://api.basedagents.ai';
|
|
19
|
-
const VERSION = '0.
|
|
27
|
+
const VERSION = '0.2.0';
|
|
28
|
+
const AUTH_HELP = 'Messaging requires a keypair. Set BASEDAGENTS_KEYPAIR_PATH to a JSON file ' +
|
|
29
|
+
'containing { agent_id, public_key_b58, private_key_hex }, or set ' +
|
|
30
|
+
'BASEDAGENTS_AGENT_ID + BASEDAGENTS_PRIVATE_KEY_HEX + BASEDAGENTS_PUBLIC_KEY_B58.';
|
|
31
|
+
let _keypair; // undefined = not loaded yet
|
|
32
|
+
async function getKeypair() {
|
|
33
|
+
if (_keypair !== undefined)
|
|
34
|
+
return _keypair;
|
|
35
|
+
if (process.env.BASEDAGENTS_KEYPAIR_PATH) {
|
|
36
|
+
try {
|
|
37
|
+
const raw = await readFile(process.env.BASEDAGENTS_KEYPAIR_PATH, 'utf-8');
|
|
38
|
+
const kp = JSON.parse(raw);
|
|
39
|
+
if (kp.agent_id && kp.public_key_b58 && kp.private_key_hex) {
|
|
40
|
+
_keypair = kp;
|
|
41
|
+
return _keypair;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// fall through
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const id = process.env.BASEDAGENTS_AGENT_ID;
|
|
49
|
+
const priv = process.env.BASEDAGENTS_PRIVATE_KEY_HEX;
|
|
50
|
+
const pub = process.env.BASEDAGENTS_PUBLIC_KEY_B58;
|
|
51
|
+
if (id && priv && pub) {
|
|
52
|
+
_keypair = { agent_id: id, private_key_hex: priv, public_key_b58: pub };
|
|
53
|
+
return _keypair;
|
|
54
|
+
}
|
|
55
|
+
_keypair = null;
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function sha256hex(data) {
|
|
59
|
+
return createHash('sha256').update(data).digest('hex');
|
|
60
|
+
}
|
|
61
|
+
// Base58 alphabet (Bitcoin)
|
|
62
|
+
const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
63
|
+
function base58Decode(str) {
|
|
64
|
+
let num = 0n;
|
|
65
|
+
for (const c of str) {
|
|
66
|
+
const idx = B58.indexOf(c);
|
|
67
|
+
if (idx === -1)
|
|
68
|
+
throw new Error(`Invalid base58 character: ${c}`);
|
|
69
|
+
num = num * 58n + BigInt(idx);
|
|
70
|
+
}
|
|
71
|
+
let hex = num.toString(16);
|
|
72
|
+
if (hex.length % 2)
|
|
73
|
+
hex = '0' + hex;
|
|
74
|
+
let leadingZeros = 0;
|
|
75
|
+
for (const c of str) {
|
|
76
|
+
if (c === '1')
|
|
77
|
+
leadingZeros++;
|
|
78
|
+
else
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
const bytes = new Uint8Array(leadingZeros + hex.length / 2);
|
|
82
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
83
|
+
bytes[leadingZeros + i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
84
|
+
}
|
|
85
|
+
return bytes;
|
|
86
|
+
}
|
|
87
|
+
async function signRequest(kp, method, path, body) {
|
|
88
|
+
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
89
|
+
const bodyHash = sha256hex(body);
|
|
90
|
+
const message = `${method}:${path}:${timestamp}:${bodyHash}`;
|
|
91
|
+
const msgBytes = new TextEncoder().encode(message);
|
|
92
|
+
const privKey = Uint8Array.from(Buffer.from(kp.private_key_hex, 'hex'));
|
|
93
|
+
const sig = await ed.signAsync(msgBytes, privKey);
|
|
94
|
+
const base64Sig = Buffer.from(sig).toString('base64');
|
|
95
|
+
return {
|
|
96
|
+
authorization: `AgentSig ${kp.public_key_b58}:${base64Sig}`,
|
|
97
|
+
timestamp,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
20
100
|
// ─── API helpers ────────────────────────────────────────────────────────────
|
|
21
101
|
async function apiFetch(path) {
|
|
22
102
|
const res = await fetch(`${API}${path}`, {
|
|
23
103
|
headers: { 'User-Agent': `basedagents-mcp/${VERSION}` },
|
|
24
104
|
});
|
|
25
105
|
if (!res.ok) {
|
|
26
|
-
// Consume body without leaking server internals to callers
|
|
27
106
|
await res.text().catch(() => { });
|
|
28
107
|
throw new Error(`BasedAgents API returned ${res.status} for ${path}`);
|
|
29
108
|
}
|
|
30
109
|
return res.json();
|
|
31
110
|
}
|
|
111
|
+
async function authedFetch(method, path, body) {
|
|
112
|
+
const kp = await getKeypair();
|
|
113
|
+
if (!kp)
|
|
114
|
+
throw new Error(AUTH_HELP);
|
|
115
|
+
const bodyStr = body ? JSON.stringify(body) : '';
|
|
116
|
+
const { authorization, timestamp } = await signRequest(kp, method, path, bodyStr);
|
|
117
|
+
const headers = {
|
|
118
|
+
'User-Agent': `basedagents-mcp/${VERSION}`,
|
|
119
|
+
'Authorization': authorization,
|
|
120
|
+
'X-Timestamp': timestamp,
|
|
121
|
+
};
|
|
122
|
+
if (body)
|
|
123
|
+
headers['Content-Type'] = 'application/json';
|
|
124
|
+
const res = await fetch(`${API}${path}`, {
|
|
125
|
+
method,
|
|
126
|
+
headers,
|
|
127
|
+
...(body ? { body: bodyStr } : {}),
|
|
128
|
+
});
|
|
129
|
+
if (!res.ok) {
|
|
130
|
+
const text = await res.text().catch(() => '');
|
|
131
|
+
throw new Error(`BasedAgents API returned ${res.status} for ${method} ${path}: ${text}`);
|
|
132
|
+
}
|
|
133
|
+
return res.json();
|
|
134
|
+
}
|
|
32
135
|
function formatAgent(a) {
|
|
33
136
|
const lines = [
|
|
34
137
|
`## ${a.name} (${a.agent_id})`,
|
|
@@ -212,6 +315,140 @@ server.tool('get_chain_entry', 'Look up a specific entry in the BasedAgents hash
|
|
|
212
315
|
].filter(Boolean);
|
|
213
316
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
214
317
|
});
|
|
318
|
+
// ─── Messaging helpers ───────────────────────────────────────────────────────
|
|
319
|
+
function noAuthResult() {
|
|
320
|
+
return {
|
|
321
|
+
content: [{ type: 'text', text: `**Auth not configured.**\n\n${AUTH_HELP}` }],
|
|
322
|
+
isError: true,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function formatMessage(m) {
|
|
326
|
+
const lines = [
|
|
327
|
+
`### ${m.subject ?? '(no subject)'}`,
|
|
328
|
+
`**ID:** \`${m.id}\` | **Type:** ${m.type} | **Status:** ${m.status}`,
|
|
329
|
+
`**From:** \`${m.from_agent_id}\` → **To:** \`${m.to_agent_id}\``,
|
|
330
|
+
`**Date:** ${m.created_at?.slice(0, 19).replace('T', ' ')} UTC`,
|
|
331
|
+
];
|
|
332
|
+
if (m.reply_to_message_id)
|
|
333
|
+
lines.push(`**Reply to:** \`${m.reply_to_message_id}\``);
|
|
334
|
+
lines.push('', m.body);
|
|
335
|
+
return lines.join('\n');
|
|
336
|
+
}
|
|
337
|
+
function formatMessageSummary(m) {
|
|
338
|
+
const status = m.status === 'pending' ? '● ' : m.status === 'delivered' ? '◉ ' : '';
|
|
339
|
+
const date = m.created_at?.slice(0, 10) ?? '';
|
|
340
|
+
return (`${status}**${m.subject ?? '(no subject)'}** — \`${m.id}\`\n` +
|
|
341
|
+
` ${m.type} | ${m.status} | from \`${m.from_agent_id}\` | ${date}`);
|
|
342
|
+
}
|
|
343
|
+
// ── check_messages ──────────────────────────────────────────────────────────
|
|
344
|
+
server.tool('check_messages', 'Check your agent inbox for received messages. Requires keypair auth.', {
|
|
345
|
+
status: z.enum(['pending', 'delivered', 'read']).optional().describe('Filter by message status'),
|
|
346
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max messages to return (default 10)'),
|
|
347
|
+
}, async (params) => {
|
|
348
|
+
const kp = await getKeypair();
|
|
349
|
+
if (!kp)
|
|
350
|
+
return noAuthResult();
|
|
351
|
+
const qs = new URLSearchParams();
|
|
352
|
+
if (params.status)
|
|
353
|
+
qs.set('status', params.status);
|
|
354
|
+
if (params.limit)
|
|
355
|
+
qs.set('limit', String(params.limit));
|
|
356
|
+
const path = `/v1/agents/${encodeURIComponent(kp.agent_id)}/messages${qs.toString() ? `?${qs}` : ''}`;
|
|
357
|
+
const data = await authedFetch('GET', path);
|
|
358
|
+
if (!data.messages.length) {
|
|
359
|
+
return { content: [{ type: 'text', text: 'No messages found.' }] };
|
|
360
|
+
}
|
|
361
|
+
const total = data.pagination?.total ?? data.messages.length;
|
|
362
|
+
const lines = [
|
|
363
|
+
`## Inbox (${total} message${total !== 1 ? 's' : ''})\n`,
|
|
364
|
+
...data.messages.map(formatMessageSummary),
|
|
365
|
+
'',
|
|
366
|
+
'Use `read_message` with a message ID to read the full message.',
|
|
367
|
+
];
|
|
368
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
369
|
+
});
|
|
370
|
+
// ── check_sent_messages ─────────────────────────────────────────────────────
|
|
371
|
+
server.tool('check_sent_messages', 'Check messages your agent has sent. Requires keypair auth.', {
|
|
372
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max messages to return (default 10)'),
|
|
373
|
+
}, async (params) => {
|
|
374
|
+
const kp = await getKeypair();
|
|
375
|
+
if (!kp)
|
|
376
|
+
return noAuthResult();
|
|
377
|
+
const qs = new URLSearchParams();
|
|
378
|
+
if (params.limit)
|
|
379
|
+
qs.set('limit', String(params.limit));
|
|
380
|
+
const path = `/v1/agents/${encodeURIComponent(kp.agent_id)}/messages/sent${qs.toString() ? `?${qs}` : ''}`;
|
|
381
|
+
const data = await authedFetch('GET', path);
|
|
382
|
+
if (!data.messages.length) {
|
|
383
|
+
return { content: [{ type: 'text', text: 'No sent messages found.' }] };
|
|
384
|
+
}
|
|
385
|
+
const total = data.pagination?.total ?? data.messages.length;
|
|
386
|
+
const lines = [
|
|
387
|
+
`## Sent Messages (${total})\n`,
|
|
388
|
+
...data.messages.map((m) => {
|
|
389
|
+
const date = m.created_at?.slice(0, 10) ?? '';
|
|
390
|
+
return (`**${m.subject ?? '(no subject)'}** — \`${m.id}\`\n` +
|
|
391
|
+
` ${m.type} | ${m.status} | to \`${m.to_agent_id}\` | ${date}`);
|
|
392
|
+
}),
|
|
393
|
+
'',
|
|
394
|
+
'Use `read_message` with a message ID for full details.',
|
|
395
|
+
];
|
|
396
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
397
|
+
});
|
|
398
|
+
// ── read_message ────────────────────────────────────────────────────────────
|
|
399
|
+
server.tool('read_message', 'Read a specific message by its ID. Auto-marks the message as read if you are the recipient. Requires keypair auth.', {
|
|
400
|
+
message_id: z.string().describe('The message ID, e.g. msg_abc123'),
|
|
401
|
+
}, async ({ message_id }) => {
|
|
402
|
+
const kp = await getKeypair();
|
|
403
|
+
if (!kp)
|
|
404
|
+
return noAuthResult();
|
|
405
|
+
const path = `/v1/messages/${encodeURIComponent(message_id)}`;
|
|
406
|
+
const data = await authedFetch('GET', path);
|
|
407
|
+
return { content: [{ type: 'text', text: formatMessage(data) }] };
|
|
408
|
+
});
|
|
409
|
+
// ── send_message ────────────────────────────────────────────────────────────
|
|
410
|
+
server.tool('send_message', 'Send a message to another agent. Requires keypair auth.', {
|
|
411
|
+
to_agent_id: z.string().describe('The recipient agent ID, e.g. ag_7Xk9mP2qR8nK4vL3'),
|
|
412
|
+
type: z.enum(['message', 'task_request']).describe('Message type'),
|
|
413
|
+
subject: z.string().describe('Message subject line'),
|
|
414
|
+
body: z.string().describe('Message body text'),
|
|
415
|
+
}, async ({ to_agent_id, type, subject, body }) => {
|
|
416
|
+
const kp = await getKeypair();
|
|
417
|
+
if (!kp)
|
|
418
|
+
return noAuthResult();
|
|
419
|
+
const path = `/v1/agents/${encodeURIComponent(to_agent_id)}/messages`;
|
|
420
|
+
const data = await authedFetch('POST', path, { type, subject, body });
|
|
421
|
+
const lines = [
|
|
422
|
+
`Message sent successfully.`,
|
|
423
|
+
'',
|
|
424
|
+
`**ID:** \`${data.id}\``,
|
|
425
|
+
`**To:** \`${to_agent_id}\``,
|
|
426
|
+
`**Subject:** ${subject}`,
|
|
427
|
+
`**Status:** ${data.status ?? 'pending'}`,
|
|
428
|
+
];
|
|
429
|
+
if (data.webhook_delivered)
|
|
430
|
+
lines.push(`**Webhook:** delivered`);
|
|
431
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
432
|
+
});
|
|
433
|
+
// ── reply_message ───────────────────────────────────────────────────────────
|
|
434
|
+
server.tool('reply_message', 'Reply to a received message. Only the original recipient can reply. Requires keypair auth.', {
|
|
435
|
+
message_id: z.string().describe('The message ID to reply to'),
|
|
436
|
+
body: z.string().describe('Reply body text'),
|
|
437
|
+
}, async ({ message_id, body }) => {
|
|
438
|
+
const kp = await getKeypair();
|
|
439
|
+
if (!kp)
|
|
440
|
+
return noAuthResult();
|
|
441
|
+
const path = `/v1/messages/${encodeURIComponent(message_id)}/reply`;
|
|
442
|
+
const data = await authedFetch('POST', path, { body });
|
|
443
|
+
const lines = [
|
|
444
|
+
`Reply sent successfully.`,
|
|
445
|
+
'',
|
|
446
|
+
`**Reply ID:** \`${data.id}\``,
|
|
447
|
+
`**In reply to:** \`${message_id}\``,
|
|
448
|
+
`**Status:** ${data.status ?? 'pending'}`,
|
|
449
|
+
];
|
|
450
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
451
|
+
});
|
|
215
452
|
// ─── Start ──────────────────────────────────────────────────────────────────
|
|
216
453
|
async function main() {
|
|
217
454
|
const transport = new StdioServerTransport();
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basedagents/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"mcpName": "io.github.maxfain/basedagents",
|
|
5
|
+
"description": "MCP server for the BasedAgents identity & reputation network \u2014 search agents, get profiles, check reputation",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "dist/index.js",
|
|
7
8
|
"types": "dist/index.d.ts",
|
|
@@ -23,7 +24,6 @@
|
|
|
23
24
|
"mcp",
|
|
24
25
|
"model-context-protocol",
|
|
25
26
|
"ai-agents",
|
|
26
|
-
"agent-registry",
|
|
27
27
|
"basedagents",
|
|
28
28
|
"identity",
|
|
29
29
|
"reputation"
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@modelcontextprotocol/sdk": "^1.27.1"
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
44
|
+
"@noble/ed25519": "^2.0.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"tsx": "^4.0.0",
|