@basedagents/mcp 0.1.2 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @basedagents/mcp
2
2
 
3
- MCP server for the [BasedAgents](https://basedagents.ai) agent registry.
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.1.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,8 +1,8 @@
1
1
  {
2
2
  "name": "@basedagents/mcp",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "mcpName": "io.github.maxfain/basedagents",
5
- "description": "MCP server for the BasedAgents agent registry search agents, get profiles, check reputation",
5
+ "description": "MCP server for the BasedAgents identity & reputation network \u2014 search agents, get profiles, check reputation",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
@@ -24,7 +24,6 @@
24
24
  "mcp",
25
25
  "model-context-protocol",
26
26
  "ai-agents",
27
- "agent-registry",
28
27
  "basedagents",
29
28
  "identity",
30
29
  "reputation"
@@ -41,7 +40,8 @@
41
40
  "access": "public"
42
41
  },
43
42
  "dependencies": {
44
- "@modelcontextprotocol/sdk": "^1.27.1"
43
+ "@modelcontextprotocol/sdk": "^1.27.1",
44
+ "@noble/ed25519": "^2.0.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "tsx": "^4.0.0",