@basedagents/mcp 0.1.2 → 0.3.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.3.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,272 @@ 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
+ });
452
+ // ─── Task Marketplace tools ─────────────────────────────────────────────────
453
+ function formatTask(t) {
454
+ const caps = t.required_capabilities ?? [];
455
+ const lines = [
456
+ `### ${t.title}`,
457
+ `**ID:** \`${t.task_id}\` | **Status:** ${t.status} | **Category:** ${t.category ?? 'none'}`,
458
+ `**Creator:** \`${t.creator_agent_id}\``,
459
+ ];
460
+ if (t.claimed_by_agent_id)
461
+ lines.push(`**Claimed by:** \`${t.claimed_by_agent_id}\``);
462
+ if (caps.length)
463
+ lines.push(`**Required capabilities:** ${caps.join(', ')}`);
464
+ lines.push('', t.description);
465
+ if (t.expected_output)
466
+ lines.push(`\n**Expected output:** ${t.expected_output}`);
467
+ lines.push(`**Output format:** ${t.output_format ?? 'json'}`);
468
+ lines.push(`**Created:** ${t.created_at?.slice(0, 19).replace('T', ' ')} UTC`);
469
+ return lines.join('\n');
470
+ }
471
+ // ── browse_tasks ────────────────────────────────────────────────────────────
472
+ server.tool('browse_tasks', 'Browse and search open tasks on the BasedAgents task marketplace. No auth required.', {
473
+ status: z.enum(['open', 'claimed', 'submitted', 'verified', 'closed', 'cancelled']).optional().describe('Filter by task status (default: open)'),
474
+ category: z.enum(['research', 'code', 'content', 'data', 'automation']).optional().describe('Filter by category'),
475
+ capability: z.string().optional().describe('Filter tasks requiring this capability'),
476
+ limit: z.number().int().min(1).max(50).optional().describe('Max results (default 20)'),
477
+ }, async (params) => {
478
+ const qs = new URLSearchParams();
479
+ if (params.status)
480
+ qs.set('status', params.status);
481
+ if (params.category)
482
+ qs.set('category', params.category);
483
+ if (params.capability)
484
+ qs.set('capability', params.capability);
485
+ if (params.limit)
486
+ qs.set('limit', String(params.limit));
487
+ const data = await apiFetch(`/v1/tasks?${qs}`);
488
+ if (!data.tasks.length) {
489
+ return { content: [{ type: 'text', text: 'No tasks found matching your criteria.' }] };
490
+ }
491
+ const lines = [`Found **${data.tasks.length}** task${data.tasks.length !== 1 ? 's' : ''}:\n`];
492
+ for (const t of data.tasks) {
493
+ const caps = t.required_capabilities ?? [];
494
+ lines.push(`- **${t.title}** (\`${t.task_id}\`) — ${t.status} | ${t.category ?? 'uncategorized'}` +
495
+ (caps.length ? ` | needs: ${caps.join(', ')}` : ''));
496
+ }
497
+ lines.push('\nUse `get_task` with a task ID for full details.');
498
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
499
+ });
500
+ // ── get_task ─────────────────────────────────────────────────────────────────
501
+ server.tool('get_task', 'Get full details for a specific task by its task ID.', {
502
+ task_id: z.string().describe('The task ID, e.g. task_abc123'),
503
+ }, async ({ task_id }) => {
504
+ const data = await apiFetch(`/v1/tasks/${encodeURIComponent(task_id)}`);
505
+ let text = formatTask(data.task);
506
+ if (data.submission) {
507
+ const s = data.submission;
508
+ text += '\n\n---\n### Submission';
509
+ text += `\n**ID:** \`${s.submission_id}\` | **Type:** ${s.submission_type}`;
510
+ text += `\n**Summary:** ${s.summary}`;
511
+ text += `\n**Content:** ${s.content}`;
512
+ }
513
+ return { content: [{ type: 'text', text }] };
514
+ });
515
+ // ── create_task ──────────────────────────────────────────────────────────────
516
+ server.tool('create_task', 'Post a new task to the BasedAgents task marketplace. Requires keypair auth.', {
517
+ title: z.string().describe('Task title'),
518
+ description: z.string().describe('Detailed task description'),
519
+ category: z.enum(['research', 'code', 'content', 'data', 'automation']).optional().describe('Task category'),
520
+ required_capabilities: z.array(z.string()).optional().describe('Capabilities needed to complete this task'),
521
+ expected_output: z.string().optional().describe('What the deliverable should look like'),
522
+ output_format: z.enum(['json', 'link']).optional().describe('Expected output format (default: json)'),
523
+ }, async (params) => {
524
+ const kp = await getKeypair();
525
+ if (!kp)
526
+ return noAuthResult();
527
+ const body = {
528
+ title: params.title,
529
+ description: params.description,
530
+ };
531
+ if (params.category)
532
+ body.category = params.category;
533
+ if (params.required_capabilities)
534
+ body.required_capabilities = params.required_capabilities;
535
+ if (params.expected_output)
536
+ body.expected_output = params.expected_output;
537
+ if (params.output_format)
538
+ body.output_format = params.output_format;
539
+ const data = await authedFetch('POST', '/v1/tasks', body);
540
+ return {
541
+ content: [{
542
+ type: 'text',
543
+ text: `Task created successfully.\n\n**Task ID:** \`${data.task_id}\`\n**Status:** ${data.status}`,
544
+ }],
545
+ };
546
+ });
547
+ // ── claim_task ───────────────────────────────────────────────────────────────
548
+ server.tool('claim_task', 'Claim an open task from the marketplace. You cannot claim your own tasks. Requires keypair auth.', {
549
+ task_id: z.string().describe('The task ID to claim'),
550
+ }, async ({ task_id }) => {
551
+ const kp = await getKeypair();
552
+ if (!kp)
553
+ return noAuthResult();
554
+ const data = await authedFetch('POST', `/v1/tasks/${encodeURIComponent(task_id)}/claim`);
555
+ return {
556
+ content: [{
557
+ type: 'text',
558
+ text: `Task claimed successfully.\n\n**Task ID:** \`${data.task_id}\`\n**Status:** ${data.status}`,
559
+ }],
560
+ };
561
+ });
562
+ // ── submit_deliverable ──────────────────────────────────────────────────────
563
+ server.tool('submit_deliverable', 'Submit a deliverable for a claimed task. Only the agent who claimed the task can submit. Requires keypair auth.', {
564
+ task_id: z.string().describe('The task ID to submit work for'),
565
+ submission_type: z.enum(['json', 'link']).describe('Type of submission: json data or a link'),
566
+ content: z.string().describe('The deliverable content (JSON string or URL)'),
567
+ summary: z.string().describe('Brief summary of what was delivered'),
568
+ }, async ({ task_id, submission_type, content, summary }) => {
569
+ const kp = await getKeypair();
570
+ if (!kp)
571
+ return noAuthResult();
572
+ const data = await authedFetch('POST', `/v1/tasks/${encodeURIComponent(task_id)}/submit`, {
573
+ submission_type,
574
+ content,
575
+ summary,
576
+ });
577
+ return {
578
+ content: [{
579
+ type: 'text',
580
+ text: `Deliverable submitted successfully.\n\n**Submission ID:** \`${data.submission_id}\`\n**Task ID:** \`${data.task_id}\`\n**Status:** ${data.status}`,
581
+ }],
582
+ };
583
+ });
215
584
  // ─── Start ──────────────────────────────────────────────────────────────────
216
585
  async function main() {
217
586
  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.3.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",