ruvector 0.2.18 → 0.2.19

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/README.md +20 -7
  2. package/bin/cli.js +1430 -1553
  3. package/bin/mcp-server.js +277 -930
  4. package/package.json +19 -17
package/bin/mcp-server.js CHANGED
@@ -95,71 +95,6 @@ function sanitizeNumericArg(arg, defaultVal) {
95
95
  return Number.isFinite(n) && n > 0 ? n : (defaultVal || 0);
96
96
  }
97
97
 
98
- // ── Proxy-aware fetch wrapper ───────────────────────────────────────────────
99
- let _proxyDispatcherSet = false;
100
-
101
- function getProxyUrl(targetUrl) {
102
- const NO_PROXY = process.env.NO_PROXY || process.env.no_proxy || '';
103
- if (NO_PROXY === '*') return null;
104
- if (NO_PROXY) {
105
- try {
106
- const host = new URL(targetUrl).hostname.toLowerCase();
107
- const patterns = NO_PROXY.split(',').map(p => p.trim().toLowerCase());
108
- for (const p of patterns) {
109
- if (!p) continue;
110
- if (host === p || host.endsWith(p.startsWith('.') ? p : '.' + p)) return null;
111
- }
112
- } catch {}
113
- }
114
- return process.env.HTTPS_PROXY || process.env.https_proxy
115
- || process.env.HTTP_PROXY || process.env.http_proxy
116
- || process.env.ALL_PROXY || process.env.all_proxy
117
- || null;
118
- }
119
-
120
- async function proxyFetch(url, opts) {
121
- const target = typeof url === 'string' ? url : url.toString();
122
- const proxy = getProxyUrl(target);
123
- if (!proxy) return fetch(url, opts);
124
-
125
- if (!_proxyDispatcherSet) {
126
- try {
127
- const undici = require('undici');
128
- if (undici.ProxyAgent && undici.setGlobalDispatcher) {
129
- undici.setGlobalDispatcher(new undici.ProxyAgent(proxy));
130
- _proxyDispatcherSet = true;
131
- }
132
- } catch {}
133
- }
134
- if (_proxyDispatcherSet) return fetch(url, opts);
135
-
136
- const { execFileSync } = require('child_process');
137
- const args = ['-sS', '-L', '--max-time', '30'];
138
- if (opts && opts.method) { args.push('-X', opts.method); }
139
- if (opts && opts.headers) {
140
- for (const [k, v] of Object.entries(opts.headers)) {
141
- args.push('-H', `${k}: ${v}`);
142
- }
143
- }
144
- if (opts && opts.body) { args.push('-d', typeof opts.body === 'string' ? opts.body : JSON.stringify(opts.body)); }
145
- args.push(target);
146
- try {
147
- const stdout = execFileSync('curl', args, { encoding: 'utf8', timeout: 35000 });
148
- const body = stdout.trim();
149
- return {
150
- ok: true,
151
- status: 200,
152
- statusText: 'OK',
153
- text: async () => body,
154
- json: async () => JSON.parse(body),
155
- headers: new Map(),
156
- };
157
- } catch (e) {
158
- const msg = e.stderr ? e.stderr.toString().trim() : e.message;
159
- throw new Error(`Proxy curl failed: ${msg}`);
160
- }
161
- }
162
-
163
98
  // Try to load the full IntelligenceEngine
164
99
  let IntelligenceEngine = null;
165
100
  let engineAvailable = false;
@@ -428,7 +363,7 @@ class Intelligence {
428
363
  const server = new Server(
429
364
  {
430
365
  name: 'ruvector',
431
- version: '0.2.16',
366
+ version: '0.2.0',
432
367
  },
433
368
  {
434
369
  capabilities: {
@@ -1289,12 +1224,11 @@ const TOOLS = [
1289
1224
  },
1290
1225
  {
1291
1226
  name: 'rvf_examples',
1292
- description: 'List available example .rvf files with download URLs. Supports filtering by name, description, or category.',
1227
+ description: 'List available example .rvf files with download URLs from the ruvector repository',
1293
1228
  inputSchema: {
1294
1229
  type: 'object',
1295
1230
  properties: {
1296
- filter: { type: 'string', description: 'Filter examples by name or description substring' },
1297
- category: { type: 'string', description: 'Filter by category (core, ai, security, compute, lineage, industry, network, integration)' }
1231
+ filter: { type: 'string', description: 'Filter examples by name or description substring' }
1298
1232
  },
1299
1233
  required: []
1300
1234
  }
@@ -1336,7 +1270,8 @@ const TOOLS = [
1336
1270
  required: ['query']
1337
1271
  }
1338
1272
  },
1339
- // ── Brain Tools (11) ── Shared intelligence via @ruvector/pi-brain ──
1273
+
1274
+ // ── Brain Tools (Shared Intelligence) ─────────────────────────────────
1340
1275
  {
1341
1276
  name: 'brain_search',
1342
1277
  description: 'Semantic search across shared brain knowledge',
@@ -1344,67 +1279,64 @@ const TOOLS = [
1344
1279
  type: 'object',
1345
1280
  properties: {
1346
1281
  query: { type: 'string', description: 'Search query' },
1347
- category: { type: 'string', description: 'Filter by category (pattern, solution, architecture, convention, security, performance, tooling)' },
1348
- limit: { type: 'number', description: 'Max results (default 10)' }
1282
+ limit: { type: 'number', description: 'Max results to return', default: 10 },
1283
+ category: { type: 'string', description: 'Filter by category (optional)' }
1349
1284
  },
1350
1285
  required: ['query']
1351
1286
  }
1352
1287
  },
1353
1288
  {
1354
1289
  name: 'brain_share',
1355
- description: 'Share a learning or pattern with the collective brain',
1290
+ description: 'Share knowledge with the collective brain',
1356
1291
  inputSchema: {
1357
1292
  type: 'object',
1358
1293
  properties: {
1359
1294
  title: { type: 'string', description: 'Title of the knowledge entry' },
1360
- content: { type: 'string', description: 'Content/description of the knowledge' },
1361
- category: { type: 'string', description: 'Category (pattern, solution, architecture, convention, security, performance, tooling)' },
1362
- tags: { type: 'string', description: 'Comma-separated tags' },
1363
- code_snippet: { type: 'string', description: 'Optional code snippet' }
1295
+ content: { type: 'string', description: 'Knowledge content to share' },
1296
+ category: { type: 'string', description: 'Category (pattern, architecture, security, etc.)', default: 'pattern' },
1297
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags for the entry' }
1364
1298
  },
1365
- required: ['title', 'content', 'category']
1299
+ required: ['title', 'content']
1366
1300
  }
1367
1301
  },
1368
1302
  {
1369
1303
  name: 'brain_get',
1370
- description: 'Retrieve a specific memory by ID with full provenance',
1304
+ description: 'Retrieve a specific memory by ID with provenance',
1371
1305
  inputSchema: {
1372
1306
  type: 'object',
1373
1307
  properties: {
1374
- id: { type: 'string', description: 'Memory ID' }
1308
+ id: { type: 'string', description: 'Memory ID to retrieve' }
1375
1309
  },
1376
1310
  required: ['id']
1377
1311
  }
1378
1312
  },
1379
1313
  {
1380
1314
  name: 'brain_vote',
1381
- description: 'Quality-gate a memory with an up or down vote',
1315
+ description: 'Vote on knowledge quality',
1382
1316
  inputSchema: {
1383
1317
  type: 'object',
1384
1318
  properties: {
1385
- id: { type: 'string', description: 'Memory ID' },
1386
- direction: { type: 'string', description: 'Vote direction: up or down' }
1319
+ id: { type: 'string', description: 'Memory ID to vote on' },
1320
+ direction: { type: 'string', enum: ['up', 'down'], description: 'Vote direction' }
1387
1321
  },
1388
1322
  required: ['id', 'direction']
1389
1323
  }
1390
1324
  },
1391
1325
  {
1392
1326
  name: 'brain_list',
1393
- description: 'List recent shared memories filtered by category or quality',
1327
+ description: 'List recent shared memories',
1394
1328
  inputSchema: {
1395
1329
  type: 'object',
1396
1330
  properties: {
1397
- category: { type: 'string', description: 'Filter by category' },
1398
- limit: { type: 'number', description: 'Max results (default 20)' },
1399
- offset: { type: 'number', description: 'Skip first N results for pagination (default 0)' },
1400
- sort: { type: 'string', description: 'Sort by: updated_at, quality, votes (default updated_at)' },
1401
- tags: { type: 'string', description: 'Filter by tags (comma-separated)' }
1402
- }
1331
+ category: { type: 'string', description: 'Filter by category (optional)' },
1332
+ limit: { type: 'number', description: 'Max results to return', default: 20 }
1333
+ },
1334
+ required: []
1403
1335
  }
1404
1336
  },
1405
1337
  {
1406
1338
  name: 'brain_delete',
1407
- description: 'Delete your own contribution from the shared brain',
1339
+ description: 'Delete your own contribution',
1408
1340
  inputSchema: {
1409
1341
  type: 'object',
1410
1342
  properties: {
@@ -1415,302 +1347,122 @@ const TOOLS = [
1415
1347
  },
1416
1348
  {
1417
1349
  name: 'brain_status',
1418
- description: 'Get shared brain system health: counts, drift, quality, graph topology',
1350
+ description: 'Get brain system health and statistics',
1419
1351
  inputSchema: {
1420
1352
  type: 'object',
1421
- properties: {}
1353
+ properties: {},
1354
+ required: []
1422
1355
  }
1423
1356
  },
1424
1357
  {
1425
1358
  name: 'brain_drift',
1426
- description: 'Check if shared knowledge has drifted from local state',
1359
+ description: 'Check knowledge drift',
1427
1360
  inputSchema: {
1428
1361
  type: 'object',
1429
1362
  properties: {
1430
- domain: { type: 'string', description: 'Domain to check drift for' }
1431
- }
1363
+ domain: { type: 'string', description: 'Domain to check drift for (optional)' }
1364
+ },
1365
+ required: []
1432
1366
  }
1433
1367
  },
1434
1368
  {
1435
1369
  name: 'brain_partition',
1436
- description: 'Get knowledge partitioned by mincut topology into clusters',
1370
+ description: 'Get knowledge topology via mincut',
1437
1371
  inputSchema: {
1438
1372
  type: 'object',
1439
1373
  properties: {
1440
- domain: { type: 'string', description: 'Domain to partition' },
1441
- min_cluster_size: { type: 'number', description: 'Minimum cluster size (default 3)' }
1442
- }
1374
+ domain: { type: 'string', description: 'Domain to partition (optional)' },
1375
+ min_cluster_size: { type: 'number', description: 'Minimum cluster size', default: 3 }
1376
+ },
1377
+ required: []
1443
1378
  }
1444
1379
  },
1445
1380
  {
1446
1381
  name: 'brain_transfer',
1447
- description: 'Apply learned priors from one knowledge domain to another',
1382
+ description: 'Transfer learned priors between domains',
1448
1383
  inputSchema: {
1449
1384
  type: 'object',
1450
1385
  properties: {
1451
- source_domain: { type: 'string', description: 'Source domain to transfer from' },
1452
- target_domain: { type: 'string', description: 'Target domain to transfer to' }
1386
+ source: { type: 'string', description: 'Source domain' },
1387
+ target: { type: 'string', description: 'Target domain' }
1453
1388
  },
1454
- required: ['source_domain', 'target_domain']
1389
+ required: ['source', 'target']
1455
1390
  }
1456
1391
  },
1457
1392
  {
1458
1393
  name: 'brain_sync',
1459
- description: 'Synchronize LoRA weights between local and shared brain',
1460
- inputSchema: {
1461
- type: 'object',
1462
- properties: {
1463
- direction: { type: 'string', description: 'Sync direction: pull, push, or both (default both)' }
1464
- }
1465
- }
1466
- },
1467
- {
1468
- name: 'brain_train',
1469
- description: 'Trigger a training cycle — runs SONA pattern learning and domain evolution on accumulated data',
1470
- inputSchema: { type: 'object', properties: {} }
1471
- },
1472
- // ── Brain AGI Tools (6) ── AGI subsystem diagnostics via direct fetch ──
1473
- {
1474
- name: 'brain_agi_status',
1475
- description: 'Combined AGI subsystem diagnostics — SONA, GWT, temporal, meta-learning, midstream',
1476
- inputSchema: { type: 'object', properties: {} }
1477
- },
1478
- {
1479
- name: 'brain_sona_stats',
1480
- description: 'SONA learning engine stats — patterns, trajectories, background ticks',
1481
- inputSchema: { type: 'object', properties: {} }
1482
- },
1483
- {
1484
- name: 'brain_temporal',
1485
- description: 'Temporal delta tracking — velocity, trend, total deltas',
1486
- inputSchema: { type: 'object', properties: {} }
1487
- },
1488
- {
1489
- name: 'brain_explore',
1490
- description: 'Meta-learning exploration — curiosity, regret, plateau status, Pareto frontier',
1491
- inputSchema: { type: 'object', properties: {} }
1492
- },
1493
- {
1494
- name: 'brain_midstream',
1495
- description: 'Midstream platform diagnostics — scheduler, attractor, solver, strange-loop',
1496
- inputSchema: { type: 'object', properties: {} }
1497
- },
1498
- {
1499
- name: 'brain_flags',
1500
- description: 'Show backend feature flag state (RVF, AGI, midstream flags)',
1501
- inputSchema: { type: 'object', properties: {} }
1502
- },
1503
- // ── Brainpedia Page Tools (5) ── Knowledge page management ──
1504
- {
1505
- name: 'brain_page_list',
1506
- description: 'List Brainpedia knowledge pages with pagination and status filter',
1507
- inputSchema: {
1508
- type: 'object',
1509
- properties: {
1510
- limit: { type: 'number', description: 'Max pages to return (default 20)' },
1511
- offset: { type: 'number', description: 'Skip first N pages for pagination (default 0)' },
1512
- status: { type: 'string', description: 'Filter by status: draft, canonical, contested, archived' }
1513
- }
1514
- }
1515
- },
1516
- {
1517
- name: 'brain_page_get',
1518
- description: 'Get a Brainpedia page by ID with delta log and evidence links',
1519
- inputSchema: {
1520
- type: 'object',
1521
- properties: {
1522
- id: { type: 'string', description: 'Page ID (UUID or slug)' }
1523
- },
1524
- required: ['id']
1525
- }
1526
- },
1527
- {
1528
- name: 'brain_page_create',
1529
- description: 'Create a new Brainpedia knowledge page (requires reputation >= 0.5)',
1530
- inputSchema: {
1531
- type: 'object',
1532
- properties: {
1533
- title: { type: 'string', description: 'Page title' },
1534
- content: { type: 'string', description: 'Page content body' },
1535
- category: { type: 'string', description: 'Category (pattern, solution, architecture, convention, security, performance, tooling)' },
1536
- tags: { type: 'string', description: 'Comma-separated tags' },
1537
- code_snippet: { type: 'string', description: 'Optional code snippet' }
1538
- },
1539
- required: ['title', 'content', 'category']
1540
- }
1541
- },
1542
- {
1543
- name: 'brain_page_update',
1544
- description: 'Submit a delta (correction, extension, evidence) to a Brainpedia page',
1545
- inputSchema: {
1546
- type: 'object',
1547
- properties: {
1548
- page_id: { type: 'string', description: 'Page ID to update' },
1549
- delta_type: { type: 'string', description: 'Delta type: correction, extension, evidence, deprecation' },
1550
- content_diff: { type: 'string', description: 'Content diff or new content' },
1551
- evidence_links: { type: 'string', description: 'JSON array of evidence links' }
1552
- },
1553
- required: ['page_id', 'content_diff']
1554
- }
1555
- },
1556
- {
1557
- name: 'brain_page_delete',
1558
- description: 'Delete a Brainpedia page by ID',
1394
+ description: 'Sync LoRA weights',
1559
1395
  inputSchema: {
1560
1396
  type: 'object',
1561
1397
  properties: {
1562
- id: { type: 'string', description: 'Page ID to delete' }
1398
+ direction: { type: 'string', enum: ['pull', 'push', 'both'], description: 'Sync direction', default: 'both' }
1563
1399
  },
1564
- required: ['id']
1565
- }
1566
- },
1567
- // ── WASM Node Tools (4) ── Executable compute node management ──
1568
- {
1569
- name: 'brain_node_list',
1570
- description: 'List published WASM compute nodes',
1571
- inputSchema: {
1572
- type: 'object',
1573
- properties: {
1574
- limit: { type: 'number', description: 'Max nodes to return (default 20)' }
1575
- }
1576
- }
1577
- },
1578
- {
1579
- name: 'brain_node_get',
1580
- description: 'Get WASM compute node metadata and conformance vectors',
1581
- inputSchema: {
1582
- type: 'object',
1583
- properties: {
1584
- id: { type: 'string', description: 'Node ID' }
1585
- },
1586
- required: ['id']
1587
- }
1588
- },
1589
- {
1590
- name: 'brain_node_publish',
1591
- description: 'Publish a WASM compute node to the shared brain network',
1592
- inputSchema: {
1593
- type: 'object',
1594
- properties: {
1595
- name: { type: 'string', description: 'Node name' },
1596
- wasm_base64: { type: 'string', description: 'Base64-encoded WASM binary' },
1597
- description: { type: 'string', description: 'Node description' },
1598
- conformance_vectors: { type: 'string', description: 'JSON array of conformance test vectors' }
1599
- },
1600
- required: ['name', 'wasm_base64']
1601
- }
1602
- },
1603
- {
1604
- name: 'brain_node_revoke',
1605
- description: 'Revoke a published WASM compute node',
1606
- inputSchema: {
1607
- type: 'object',
1608
- properties: {
1609
- id: { type: 'string', description: 'Node ID to revoke' }
1610
- },
1611
- required: ['id']
1612
- }
1613
- },
1614
- // ── Midstream Tools (6) ── Real-time streaming analysis platform ──
1615
- {
1616
- name: 'midstream_status',
1617
- description: 'Full midstream platform diagnostics — scheduler, attractor, solver, strange-loop',
1618
- inputSchema: { type: 'object', properties: {} }
1619
- },
1620
- {
1621
- name: 'midstream_attractor',
1622
- description: 'Attractor categories with Lyapunov exponent analysis',
1623
- inputSchema: {
1624
- type: 'object',
1625
- properties: {
1626
- category: { type: 'string', description: 'Optional category to filter (e.g., pattern, solution)' }
1627
- }
1628
- }
1629
- },
1630
- {
1631
- name: 'midstream_scheduler',
1632
- description: 'Nanosecond scheduler performance metrics — ticks, tasks/sec',
1633
- inputSchema: { type: 'object', properties: {} }
1634
- },
1635
- {
1636
- name: 'midstream_benchmark',
1637
- description: 'Run sequential + concurrent latency benchmark against brain backend',
1638
- inputSchema: {
1639
- type: 'object',
1640
- properties: {
1641
- concurrent_count: { type: 'number', description: 'Number of concurrent search requests (default 20, max 100)' }
1642
- }
1643
- }
1644
- },
1645
- {
1646
- name: 'midstream_search',
1647
- description: 'Semantic search with midstream scoring metadata in response',
1648
- inputSchema: {
1649
- type: 'object',
1650
- properties: {
1651
- query: { type: 'string', description: 'Search query' },
1652
- limit: { type: 'number', description: 'Max results (default 10)' }
1653
- },
1654
- required: ['query']
1400
+ required: []
1655
1401
  }
1656
1402
  },
1657
- {
1658
- name: 'midstream_health',
1659
- description: 'Combined health + midstream subsystem check',
1660
- inputSchema: { type: 'object', properties: {} }
1661
- },
1662
- // ── Edge Tools (4) ── Distributed compute via @ruvector/edge-net ──
1403
+
1404
+ // ── Edge Tools (Distributed Compute) ──────────────────────────────────
1663
1405
  {
1664
1406
  name: 'edge_status',
1665
- description: 'Get edge compute network status (genesis, relay, nodes, rUv supply)',
1407
+ description: 'Query edge network status',
1666
1408
  inputSchema: {
1667
1409
  type: 'object',
1668
- properties: {}
1410
+ properties: {},
1411
+ required: []
1669
1412
  }
1670
1413
  },
1671
1414
  {
1672
1415
  name: 'edge_join',
1673
- description: 'Join the edge compute network as a compute node',
1416
+ description: 'Join as compute node',
1674
1417
  inputSchema: {
1675
1418
  type: 'object',
1676
1419
  properties: {
1677
- contribution: { type: 'number', description: 'Contribution level 0.0-1.0 (default 0.3)' }
1678
- }
1420
+ contribution: { type: 'number', description: 'Contribution factor (0-1)', default: 0.3 },
1421
+ key: { type: 'string', description: 'PI key (optional, defaults to PI env var)' }
1422
+ },
1423
+ required: []
1679
1424
  }
1680
1425
  },
1681
1426
  {
1682
1427
  name: 'edge_balance',
1683
- description: 'Check rUv credit balance for current identity',
1428
+ description: 'Check rUv balance',
1684
1429
  inputSchema: {
1685
1430
  type: 'object',
1686
- properties: {}
1431
+ properties: {
1432
+ key: { type: 'string', description: 'PI key (optional, defaults to PI env var)' }
1433
+ },
1434
+ required: []
1687
1435
  }
1688
1436
  },
1689
1437
  {
1690
1438
  name: 'edge_tasks',
1691
- description: 'List available distributed compute tasks on the edge network',
1439
+ description: 'List available distributed compute tasks',
1692
1440
  inputSchema: {
1693
1441
  type: 'object',
1694
- properties: {
1695
- limit: { type: 'number', description: 'Max tasks to return (default 20)' }
1696
- }
1442
+ properties: {},
1443
+ required: []
1697
1444
  }
1698
1445
  },
1699
- // ── Identity Tools (2) ── Pi key management ──
1446
+
1447
+ // ── Identity Tools (PI Key Management) ────────────────────────────────
1700
1448
  {
1701
1449
  name: 'identity_generate',
1702
- description: 'Generate a new pi key and derive pseudonym',
1450
+ description: 'Generate a new PI key with SHAKE-256 pseudonym',
1703
1451
  inputSchema: {
1704
1452
  type: 'object',
1705
- properties: {}
1453
+ properties: {},
1454
+ required: []
1706
1455
  }
1707
1456
  },
1708
1457
  {
1709
1458
  name: 'identity_show',
1710
- description: 'Show current pi key pseudonym and derived identities',
1459
+ description: 'Show current identity derived from PI key',
1711
1460
  inputSchema: {
1712
1461
  type: 'object',
1713
- properties: {}
1462
+ properties: {
1463
+ key: { type: 'string', description: 'PI key (optional, defaults to PI env var)' }
1464
+ },
1465
+ required: []
1714
1466
  }
1715
1467
  }
1716
1468
  ];
@@ -3054,15 +2806,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3054
2806
  }
3055
2807
 
3056
2808
  case 'workers_create': {
3057
- const name = sanitizeShellArg(args.name);
3058
- const preset = sanitizeShellArg(args.preset || 'quick-scan');
3059
- const triggers = args.triggers ? sanitizeShellArg(args.triggers) : null;
3060
- if (!name) {
3061
- return { content: [{ type: 'text', text: JSON.stringify({
3062
- success: false,
3063
- error: 'Invalid worker name'
3064
- }, null, 2) }] };
3065
- }
2809
+ const name = args.name;
2810
+ const preset = args.preset || 'quick-scan';
2811
+ const triggers = args.triggers;
3066
2812
  try {
3067
2813
  let cmd = `npx agentic-flow@alpha workers create "${name}" --preset ${preset}`;
3068
2814
  if (triggers) cmd += ` --triggers "${triggers}"`;
@@ -3292,85 +3038,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3292
3038
  }
3293
3039
 
3294
3040
  case 'rvf_examples': {
3295
- const os = require('os');
3296
- const GCS_MANIFEST = 'https://storage.googleapis.com/ruvector-examples/manifest.json';
3297
- const GITHUB_RAW = 'https://raw.githubusercontent.com/ruvnet/ruvector/main/examples/rvf/output';
3298
- const cacheDir = path.join(os.homedir(), '.ruvector', 'examples');
3299
- const manifestPath = path.join(cacheDir, 'manifest.json');
3300
-
3301
- let manifest;
3302
- // Try cache first
3303
- if (fs.existsSync(manifestPath)) {
3304
- try {
3305
- const stat = fs.statSync(manifestPath);
3306
- if (Date.now() - stat.mtimeMs < 3600000) {
3307
- manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
3308
- }
3309
- } catch {}
3310
- }
3311
-
3312
- // Fetch from GCS if no fresh cache
3313
- if (!manifest) {
3314
- try {
3315
- const resp = await proxyFetch(GCS_MANIFEST, { signal: AbortSignal.timeout(15000) });
3316
- if (resp.ok) {
3317
- manifest = await resp.json();
3318
- try {
3319
- fs.mkdirSync(cacheDir, { recursive: true });
3320
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
3321
- } catch {}
3322
- }
3323
- } catch {}
3324
- }
3325
-
3326
- // Fallback to hardcoded
3327
- if (!manifest) {
3328
- manifest = {
3329
- version: 'builtin',
3330
- base_url: GITHUB_RAW,
3331
- examples: [
3332
- { name: 'basic_store', size_human: '152 KB', description: '1,000 vectors, dim 128', category: 'core' },
3333
- { name: 'semantic_search', size_human: '755 KB', description: 'Semantic search with HNSW', category: 'core' },
3334
- { name: 'rag_pipeline', size_human: '303 KB', description: 'RAG pipeline embeddings', category: 'core' },
3335
- { name: 'agent_memory', size_human: '32 KB', description: 'AI agent episodic memory', category: 'ai' },
3336
- { name: 'swarm_knowledge', size_human: '86 KB', description: 'Multi-agent knowledge base', category: 'ai' },
3337
- { name: 'self_booting', size_human: '31 KB', description: 'Self-booting with kernel', category: 'compute' },
3338
- { name: 'ebpf_accelerator', size_human: '153 KB', description: 'eBPF distance accelerator', category: 'compute' },
3339
- { name: 'tee_attestation', size_human: '102 KB', description: 'TEE attestation + witnesses', category: 'security' },
3340
- { name: 'claude_code_appliance', size_human: '17 KB', description: 'Claude Code appliance', category: 'integration' },
3341
- { name: 'lineage_parent', size_human: '52 KB', description: 'COW parent file', category: 'lineage' },
3342
- { name: 'financial_signals', size_human: '202 KB', description: 'Financial signals', category: 'industry' },
3343
- { name: 'progressive_index', size_human: '2.5 MB', description: 'Large-scale HNSW index', category: 'core' },
3344
- ]
3345
- };
3346
- }
3347
-
3348
- let examples = manifest.examples || [];
3349
- const baseUrl = manifest.base_url || GITHUB_RAW;
3350
-
3041
+ const BASE_URL = 'https://raw.githubusercontent.com/ruvnet/ruvector/main/examples/rvf/output';
3042
+ const examples = [
3043
+ { name: 'basic_store', size: '152 KB', desc: '1,000 vectors, dim 128' },
3044
+ { name: 'semantic_search', size: '755 KB', desc: 'Semantic search with HNSW' },
3045
+ { name: 'rag_pipeline', size: '303 KB', desc: 'RAG pipeline embeddings' },
3046
+ { name: 'agent_memory', size: '32 KB', desc: 'AI agent episodic memory' },
3047
+ { name: 'swarm_knowledge', size: '86 KB', desc: 'Multi-agent knowledge base' },
3048
+ { name: 'self_booting', size: '31 KB', desc: 'Self-booting with kernel' },
3049
+ { name: 'ebpf_accelerator', size: '153 KB', desc: 'eBPF distance accelerator' },
3050
+ { name: 'tee_attestation', size: '102 KB', desc: 'TEE attestation + witnesses' },
3051
+ { name: 'lineage_parent', size: '52 KB', desc: 'COW parent file' },
3052
+ { name: 'lineage_child', size: '26 KB', desc: 'COW child (derived)' },
3053
+ { name: 'claude_code_appliance', size: '17 KB', desc: 'Claude Code appliance' },
3054
+ { name: 'progressive_index', size: '2.5 MB', desc: 'Large-scale HNSW index' },
3055
+ ];
3056
+ let filtered = examples;
3351
3057
  if (args.filter) {
3352
3058
  const f = args.filter.toLowerCase();
3353
- examples = examples.filter(e =>
3354
- e.name.includes(f) ||
3355
- (e.description || '').toLowerCase().includes(f) ||
3356
- (e.category || '').includes(f)
3357
- );
3059
+ filtered = examples.filter(e => e.name.includes(f) || e.desc.toLowerCase().includes(f));
3358
3060
  }
3359
-
3360
- if (args.category) {
3361
- examples = examples.filter(e => e.category === args.category);
3362
- }
3363
-
3364
3061
  return { content: [{ type: 'text', text: JSON.stringify({
3365
3062
  success: true,
3366
- version: manifest.version,
3367
- total: (manifest.examples || []).length,
3368
- shown: examples.length,
3369
- examples: examples.map(e => ({
3370
- ...e,
3371
- url: `${baseUrl}/${e.name}.rvf`
3372
- })),
3373
- categories: manifest.categories || {},
3063
+ total: 45,
3064
+ shown: filtered.length,
3065
+ examples: filtered.map(e => ({ ...e, url: `${BASE_URL}/${e.name}.rvf` })),
3374
3066
  catalog: 'https://github.com/ruvnet/ruvector/tree/main/examples/rvf/output'
3375
3067
  }, null, 2) }] };
3376
3068
  }
@@ -3466,444 +3158,263 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3466
3158
  }
3467
3159
  }
3468
3160
 
3469
- // ── Brain Tool Handlers (direct fetch to pi.ruv.io) ─────────────────
3470
- case 'brain_search':
3471
- case 'brain_share':
3472
- case 'brain_get':
3473
- case 'brain_vote':
3474
- case 'brain_list':
3475
- case 'brain_delete':
3476
- case 'brain_status':
3477
- case 'brain_drift':
3478
- case 'brain_partition':
3479
- case 'brain_transfer':
3480
- case 'brain_sync':
3481
- case 'brain_train': {
3161
+ // ── Brain Tool Handlers ─────────────────────────────────────────────
3162
+ case 'brain_search': {
3482
3163
  try {
3483
- const brainUrl = process.env.BRAIN_URL || 'https://pi.ruv.io';
3484
- const brainKey = process.env.PI;
3485
- const hdrs = { 'Content-Type': 'application/json' };
3486
- if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3487
- const subCmd = name.replace('brain_', '');
3488
- let url, fetchOpts = { headers: hdrs, signal: AbortSignal.timeout(30000) };
3489
- switch (subCmd) {
3490
- case 'search': {
3491
- const p = new URLSearchParams({ q: args.query || '' });
3492
- if (args.category) p.set('category', args.category);
3493
- if (args.limit) p.set('limit', String(args.limit));
3494
- url = `${brainUrl}/v1/memories/search?${p}`;
3495
- break;
3496
- }
3497
- case 'share': {
3498
- url = `${brainUrl}/v1/memories`;
3499
- fetchOpts.method = 'POST';
3500
- fetchOpts.body = JSON.stringify({ title: args.title, content: args.content, category: args.category, tags: args.tags ? args.tags.split(',').map(t => t.trim()) : [], code_snippet: args.code_snippet });
3501
- break;
3502
- }
3503
- case 'get': url = `${brainUrl}/v1/memories/${args.id}`; break;
3504
- case 'vote': {
3505
- url = `${brainUrl}/v1/memories/${args.id}/vote`;
3506
- fetchOpts.method = 'POST';
3507
- fetchOpts.body = JSON.stringify({ direction: args.direction });
3508
- break;
3509
- }
3510
- case 'list': {
3511
- const p = new URLSearchParams();
3512
- if (args.category) p.set('category', args.category);
3513
- p.set('limit', String(args.limit || 20));
3514
- if (args.offset) p.set('offset', String(args.offset));
3515
- if (args.sort) p.set('sort', args.sort);
3516
- if (args.tags) p.set('tags', args.tags);
3517
- url = `${brainUrl}/v1/memories/list?${p}`;
3518
- break;
3519
- }
3520
- case 'delete': {
3521
- url = `${brainUrl}/v1/memories/${args.id}`;
3522
- fetchOpts.method = 'DELETE';
3523
- break;
3524
- }
3525
- case 'status': url = `${brainUrl}/v1/status`; break;
3526
- case 'drift': {
3527
- const p = new URLSearchParams();
3528
- if (args.domain) p.set('domain', args.domain);
3529
- url = `${brainUrl}/v1/drift?${p}`;
3530
- break;
3531
- }
3532
- case 'partition': {
3533
- const p = new URLSearchParams();
3534
- if (args.domain) p.set('domain', args.domain);
3535
- if (args.min_cluster_size) p.set('min_cluster_size', String(args.min_cluster_size));
3536
- url = `${brainUrl}/v1/partition?${p}`;
3537
- break;
3538
- }
3539
- case 'transfer': {
3540
- url = `${brainUrl}/v1/transfer`;
3541
- fetchOpts.method = 'POST';
3542
- fetchOpts.body = JSON.stringify({ source_domain: args.source_domain, target_domain: args.target_domain });
3543
- break;
3544
- }
3545
- case 'sync': {
3546
- const p = new URLSearchParams();
3547
- if (args.direction) p.set('direction', args.direction);
3548
- url = `${brainUrl}/v1/lora/latest${p.toString() ? '?' + p : ''}`;
3549
- break;
3550
- }
3551
- case 'train': {
3552
- url = `${brainUrl}/v1/train`;
3553
- fetchOpts.method = 'POST';
3554
- fetchOpts.body = JSON.stringify({});
3555
- break;
3556
- }
3557
- }
3558
- const resp = await proxyFetch(url, fetchOpts);
3559
- if (!resp.ok) {
3560
- const errText = await resp.text().catch(() => resp.statusText);
3561
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
3562
- }
3563
- const result = (resp.status === 204 || resp.headers.get('content-length') === '0') ? {} : await resp.json();
3564
- return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3164
+ const piBrain = require('@ruvector/pi-brain');
3165
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3166
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3167
+ const key = process.env.PI || '';
3168
+ const client = new PiBrainClient({ url, key });
3169
+ const results = await client.search(args.query, { limit: args.limit || 10, category: args.category });
3170
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...results }, null, 2) }] };
3565
3171
  } catch (e) {
3172
+ if (e.code === 'MODULE_NOT_FOUND') {
3173
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3174
+ }
3566
3175
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3567
3176
  }
3568
3177
  }
3569
3178
 
3570
- // ── Brainpedia Page Tool Handlers ────────────────────────────────────
3571
- case 'brain_page_list':
3572
- case 'brain_page_get':
3573
- case 'brain_page_create':
3574
- case 'brain_page_update':
3575
- case 'brain_page_delete': {
3179
+ case 'brain_share': {
3576
3180
  try {
3577
- const brainUrl = process.env.BRAIN_URL || 'https://pi.ruv.io';
3578
- const brainKey = process.env.PI;
3579
- const hdrs = { 'Content-Type': 'application/json' };
3580
- if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3581
- let url, fetchOpts = { headers: hdrs, signal: AbortSignal.timeout(30000) };
3582
- const subCmd = name.replace('brain_page_', '');
3583
- switch (subCmd) {
3584
- case 'list': {
3585
- const p = new URLSearchParams();
3586
- if (args.limit) p.set('limit', String(args.limit));
3587
- if (args.offset) p.set('offset', String(args.offset));
3588
- if (args.status) p.set('status', args.status);
3589
- url = `${brainUrl}/v1/pages${p.toString() ? '?' + p : ''}`;
3590
- break;
3591
- }
3592
- case 'get': url = `${brainUrl}/v1/pages/${args.id}`; break;
3593
- case 'create': {
3594
- url = `${brainUrl}/v1/pages`;
3595
- fetchOpts.method = 'POST';
3596
- fetchOpts.body = JSON.stringify({ title: args.title, content: args.content, category: args.category, tags: args.tags ? args.tags.split(',').map(t => t.trim()) : [], code_snippet: args.code_snippet, evidence_links: [], embedding: [], witness_hash: '' });
3597
- break;
3598
- }
3599
- case 'update': {
3600
- url = `${brainUrl}/v1/pages/${args.page_id}/deltas`;
3601
- fetchOpts.method = 'POST';
3602
- let evidence = [];
3603
- try { if (args.evidence_links) evidence = JSON.parse(args.evidence_links); } catch {}
3604
- fetchOpts.body = JSON.stringify({ delta_type: args.delta_type || 'extension', content_diff: args.content_diff, evidence_links: evidence, witness_hash: '' });
3605
- break;
3606
- }
3607
- case 'delete': {
3608
- url = `${brainUrl}/v1/pages/${args.id}`;
3609
- fetchOpts.method = 'DELETE';
3610
- break;
3611
- }
3612
- }
3613
- const resp = await proxyFetch(url, fetchOpts);
3614
- if (!resp.ok) {
3615
- const errText = await resp.text().catch(() => resp.statusText);
3616
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
3617
- }
3618
- const result = (resp.status === 204 || resp.headers.get('content-length') === '0') ? {} : await resp.json();
3181
+ const piBrain = require('@ruvector/pi-brain');
3182
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3183
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3184
+ const key = process.env.PI || '';
3185
+ const client = new PiBrainClient({ url, key });
3186
+ const result = await client.share({ title: args.title, content: args.content, category: args.category || 'pattern', tags: args.tags });
3619
3187
  return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3620
3188
  } catch (e) {
3189
+ if (e.code === 'MODULE_NOT_FOUND') {
3190
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3191
+ }
3621
3192
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3622
3193
  }
3623
3194
  }
3624
3195
 
3625
- // ── WASM Node Tool Handlers ────────────────────────────────────────────
3626
- case 'brain_node_list':
3627
- case 'brain_node_get':
3628
- case 'brain_node_publish':
3629
- case 'brain_node_revoke': {
3196
+ case 'brain_get': {
3630
3197
  try {
3631
- const brainUrl = process.env.BRAIN_URL || 'https://pi.ruv.io';
3632
- const brainKey = process.env.PI;
3633
- const hdrs = { 'Content-Type': 'application/json' };
3634
- if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3635
- let url, fetchOpts = { headers: hdrs, signal: AbortSignal.timeout(30000) };
3636
- const subCmd = name.replace('brain_node_', '');
3637
- switch (subCmd) {
3638
- case 'list': {
3639
- const p = new URLSearchParams();
3640
- if (args.limit) p.set('limit', String(args.limit));
3641
- url = `${brainUrl}/v1/nodes${p.toString() ? '?' + p : ''}`;
3642
- break;
3643
- }
3644
- case 'get': url = `${brainUrl}/v1/nodes/${args.id}`; break;
3645
- case 'publish': {
3646
- url = `${brainUrl}/v1/nodes`;
3647
- fetchOpts.method = 'POST';
3648
- let vectors = [];
3649
- try { if (args.conformance_vectors) vectors = JSON.parse(args.conformance_vectors); } catch {}
3650
- fetchOpts.body = JSON.stringify({ name: args.name, wasm_base64: args.wasm_base64, description: args.description || '', conformance_vectors: vectors });
3651
- break;
3652
- }
3653
- case 'revoke': {
3654
- url = `${brainUrl}/v1/nodes/${args.id}/revoke`;
3655
- fetchOpts.method = 'POST';
3656
- fetchOpts.body = JSON.stringify({});
3657
- break;
3658
- }
3659
- }
3660
- const resp = await proxyFetch(url, fetchOpts);
3661
- if (!resp.ok) {
3662
- const errText = await resp.text().catch(() => resp.statusText);
3663
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
3664
- }
3665
- const result = (resp.status === 204 || resp.headers.get('content-length') === '0') ? {} : await resp.json();
3666
- return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...(Array.isArray(result) ? { items: result, count: result.length } : result) }, null, 2) }] };
3198
+ const piBrain = require('@ruvector/pi-brain');
3199
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3200
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3201
+ const key = process.env.PI || '';
3202
+ const client = new PiBrainClient({ url, key });
3203
+ const result = await client.get(args.id);
3204
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3667
3205
  } catch (e) {
3206
+ if (e.code === 'MODULE_NOT_FOUND') {
3207
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3208
+ }
3668
3209
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3669
3210
  }
3670
3211
  }
3671
3212
 
3672
- // ── Brain AGI Tool Handlers ────────────────────────────────────────────
3673
- case 'brain_agi_status':
3674
- case 'brain_sona_stats':
3675
- case 'brain_temporal':
3676
- case 'brain_explore':
3677
- case 'brain_midstream':
3678
- case 'brain_flags': {
3213
+ case 'brain_vote': {
3679
3214
  try {
3680
- const brainUrl = process.env.BRAIN_URL || 'https://pi.ruv.io';
3681
- const brainKey = process.env.PI;
3682
- const hdrs = { 'Content-Type': 'application/json' };
3683
- if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3684
-
3685
- const endpointMap = {
3686
- brain_agi_status: '/v1/status',
3687
- brain_sona_stats: '/v1/sona/stats',
3688
- brain_temporal: '/v1/temporal',
3689
- brain_explore: '/v1/explore',
3690
- brain_midstream: '/v1/midstream',
3691
- brain_flags: '/v1/status',
3692
- };
3693
- const endpoint = endpointMap[name];
3694
- const resp = await proxyFetch(`${brainUrl}${endpoint}`, { headers: hdrs, signal: AbortSignal.timeout(30000) });
3695
- if (!resp.ok) {
3696
- const errText = await resp.text().catch(() => resp.statusText);
3697
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
3698
- }
3699
- let data = await resp.json();
3700
-
3701
- // For brain_flags, extract only flag-related fields
3702
- if (name === 'brain_flags') {
3703
- const flags = {};
3704
- for (const [k, v] of Object.entries(data)) {
3705
- if (typeof v === 'boolean' || k.startsWith('rvf_') || k.endsWith('_enabled') || (k.startsWith('midstream_') && typeof v === 'boolean')) {
3706
- flags[k] = v;
3707
- }
3708
- }
3709
- data = flags;
3710
- }
3711
-
3712
- // For brain_agi_status, extract AGI-specific fields
3713
- if (name === 'brain_agi_status') {
3714
- const agiFields = {};
3715
- const agiKeys = ['sona_patterns', 'sona_trajectories', 'sona_background_ticks', 'gwt_workspace_load', 'gwt_avg_salience', 'knowledge_velocity', 'temporal_deltas', 'temporal_trend', 'meta_avg_regret', 'meta_plateau_status', 'midstream_scheduler_ticks', 'midstream_attractor_categories', 'midstream_strange_loop_version'];
3716
- for (const k of agiKeys) {
3717
- if (data[k] !== undefined) agiFields[k] = data[k];
3718
- }
3719
- data = Object.keys(agiFields).length > 0 ? agiFields : data;
3215
+ const piBrain = require('@ruvector/pi-brain');
3216
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3217
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3218
+ const key = process.env.PI || '';
3219
+ const client = new PiBrainClient({ url, key });
3220
+ const result = await client.vote(args.id, args.direction);
3221
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3222
+ } catch (e) {
3223
+ if (e.code === 'MODULE_NOT_FOUND') {
3224
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3720
3225
  }
3226
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3227
+ }
3228
+ }
3721
3229
 
3722
- return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...data }, null, 2) }] };
3230
+ case 'brain_list': {
3231
+ try {
3232
+ const piBrain = require('@ruvector/pi-brain');
3233
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3234
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3235
+ const key = process.env.PI || '';
3236
+ const client = new PiBrainClient({ url, key });
3237
+ const results = await client.list({ category: args.category, limit: args.limit || 20 });
3238
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...results }, null, 2) }] };
3723
3239
  } catch (e) {
3240
+ if (e.code === 'MODULE_NOT_FOUND') {
3241
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3242
+ }
3724
3243
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3725
3244
  }
3726
3245
  }
3727
3246
 
3728
- // ── Midstream Tool Handlers ────────────────────────────────────────────
3729
- case 'midstream_status':
3730
- case 'midstream_attractor':
3731
- case 'midstream_scheduler': {
3247
+ case 'brain_delete': {
3732
3248
  try {
3733
- const brainUrl = process.env.BRAIN_URL || 'https://pi.ruv.io';
3734
- const brainKey = process.env.PI;
3735
- const hdrs = { 'Content-Type': 'application/json' };
3736
- if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3737
-
3738
- const resp = await proxyFetch(`${brainUrl}/v1/midstream`, { headers: hdrs, signal: AbortSignal.timeout(30000) });
3739
- if (!resp.ok) {
3740
- const errText = await resp.text().catch(() => resp.statusText);
3741
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
3249
+ const piBrain = require('@ruvector/pi-brain');
3250
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3251
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3252
+ const key = process.env.PI || '';
3253
+ const client = new PiBrainClient({ url, key });
3254
+ const result = await client.delete(args.id);
3255
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3256
+ } catch (e) {
3257
+ if (e.code === 'MODULE_NOT_FOUND') {
3258
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3742
3259
  }
3743
- let data = await resp.json();
3260
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3261
+ }
3262
+ }
3744
3263
 
3745
- if (name === 'midstream_attractor') {
3746
- data = data.attractor_categories || data.attractors || data;
3747
- if (args.category && typeof data === 'object') data = data[args.category] || { error: `Category '${args.category}' not found` };
3748
- } else if (name === 'midstream_scheduler') {
3749
- data = data.scheduler || { ticks: data.scheduler_ticks || 0 };
3264
+ case 'brain_status': {
3265
+ try {
3266
+ const piBrain = require('@ruvector/pi-brain');
3267
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3268
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3269
+ const key = process.env.PI || '';
3270
+ const client = new PiBrainClient({ url, key });
3271
+ const result = await client.status();
3272
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3273
+ } catch (e) {
3274
+ if (e.code === 'MODULE_NOT_FOUND') {
3275
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3750
3276
  }
3277
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3278
+ }
3279
+ }
3751
3280
 
3752
- return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...data }, null, 2) }] };
3281
+ case 'brain_drift': {
3282
+ try {
3283
+ const piBrain = require('@ruvector/pi-brain');
3284
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3285
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3286
+ const key = process.env.PI || '';
3287
+ const client = new PiBrainClient({ url, key });
3288
+ const result = await client.drift({ domain: args.domain });
3289
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3753
3290
  } catch (e) {
3291
+ if (e.code === 'MODULE_NOT_FOUND') {
3292
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3293
+ }
3754
3294
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3755
3295
  }
3756
3296
  }
3757
3297
 
3758
- case 'midstream_benchmark': {
3298
+ case 'brain_partition': {
3759
3299
  try {
3760
- const brainUrl = process.env.BRAIN_URL || 'https://pi.ruv.io';
3761
- const brainKey = process.env.PI;
3762
- const hdrs = { 'Content-Type': 'application/json' };
3763
- if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3764
- const concurrentN = Math.min(args.concurrent_count || 20, 100);
3765
-
3766
- async function timeFetch(url) {
3767
- const start = performance.now();
3768
- const resp = await proxyFetch(url, { headers: hdrs });
3769
- return { status: resp.status, elapsed: performance.now() - start };
3300
+ const piBrain = require('@ruvector/pi-brain');
3301
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3302
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3303
+ const key = process.env.PI || '';
3304
+ const client = new PiBrainClient({ url, key });
3305
+ const result = await client.partition({ domain: args.domain, min_cluster_size: args.min_cluster_size || 3 });
3306
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3307
+ } catch (e) {
3308
+ if (e.code === 'MODULE_NOT_FOUND') {
3309
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3770
3310
  }
3311
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3312
+ }
3313
+ }
3771
3314
 
3772
- // Sequential tests
3773
- const endpoints = [
3774
- { path: '/v1/health', label: 'health' },
3775
- { path: '/v1/status', label: 'status' },
3776
- { path: '/v1/memories/search?q=test&limit=3', label: 'search' },
3777
- { path: '/v1/midstream', label: 'midstream' },
3778
- ];
3779
-
3780
- const sequential = {};
3781
- for (const ep of endpoints) {
3782
- const times = [];
3783
- for (let i = 0; i < 3; i++) {
3784
- const r = await timeFetch(brainUrl + ep.path);
3785
- times.push(r.elapsed);
3786
- }
3787
- sequential[ep.label] = {
3788
- avg_ms: +(times.reduce((a, b) => a + b, 0) / times.length).toFixed(1),
3789
- min_ms: +Math.min(...times).toFixed(1),
3790
- max_ms: +Math.max(...times).toFixed(1)
3791
- };
3315
+ case 'brain_transfer': {
3316
+ try {
3317
+ const piBrain = require('@ruvector/pi-brain');
3318
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3319
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3320
+ const key = process.env.PI || '';
3321
+ const client = new PiBrainClient({ url, key });
3322
+ const result = await client.transfer(args.source, args.target);
3323
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3324
+ } catch (e) {
3325
+ if (e.code === 'MODULE_NOT_FOUND') {
3326
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3792
3327
  }
3328
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3329
+ }
3330
+ }
3793
3331
 
3794
- // Concurrent search
3795
- const promises = [];
3796
- for (let i = 0; i < concurrentN; i++) {
3797
- promises.push(timeFetch(brainUrl + '/v1/memories/search?q=test&limit=3'));
3332
+ case 'brain_sync': {
3333
+ try {
3334
+ const piBrain = require('@ruvector/pi-brain');
3335
+ const PiBrainClient = piBrain.PiBrainClient || piBrain.default;
3336
+ const url = process.env.BRAIN_URL || 'https://pi.ruv.io';
3337
+ const key = process.env.PI || '';
3338
+ const client = new PiBrainClient({ url, key });
3339
+ const result = await client.sync({ direction: args.direction || 'both' });
3340
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3341
+ } catch (e) {
3342
+ if (e.code === 'MODULE_NOT_FOUND') {
3343
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain', hint: 'npm install @ruvector/pi-brain' }, null, 2) }] };
3798
3344
  }
3799
- const results = await Promise.all(promises);
3800
- const sorted = results.map(r => r.elapsed).sort((a, b) => a - b);
3801
- const pct = (p) => +(sorted[Math.max(0, Math.ceil(sorted.length * p / 100) - 1)]).toFixed(1);
3345
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3346
+ }
3347
+ }
3802
3348
 
3803
- return { content: [{ type: 'text', text: JSON.stringify({
3804
- success: true,
3805
- sequential,
3806
- concurrent: { count: concurrentN, p50_ms: pct(50), p90_ms: pct(90), p99_ms: pct(99) }
3807
- }, null, 2) }] };
3349
+ // ── Edge Tool Handlers ──────────────────────────────────────────────
3350
+ case 'edge_status': {
3351
+ try {
3352
+ const res = await fetch('https://edge-net-genesis-875130704813.us-central1.run.app/api/status');
3353
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
3354
+ const data = await res.json();
3355
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...data }, null, 2) }] };
3808
3356
  } catch (e) {
3809
3357
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3810
3358
  }
3811
3359
  }
3812
3360
 
3813
- case 'midstream_search': {
3361
+ case 'edge_join': {
3814
3362
  try {
3815
- const brainUrl = process.env.BRAIN_URL || 'https://pi.ruv.io';
3816
- const brainKey = process.env.PI;
3817
- const hdrs = { 'Content-Type': 'application/json' };
3818
- if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3819
- const limit = Math.min(Math.max(parseInt(args.limit) || 10, 1), 100);
3820
- const q = encodeURIComponent(args.query);
3821
- const resp = await proxyFetch(`${brainUrl}/v1/memories/search?q=${q}&limit=${limit}`, { headers: hdrs, signal: AbortSignal.timeout(30000) });
3822
- if (!resp.ok) {
3823
- const errText = await resp.text().catch(() => resp.statusText);
3824
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
3825
- }
3826
- const data = await resp.json();
3827
- return { content: [{ type: 'text', text: JSON.stringify({ success: true, results: data, count: Array.isArray(data) ? data.length : 0 }, null, 2) }] };
3363
+ const key = args.key || process.env.PI || '';
3364
+ const res = await fetch('https://edge-net-genesis-875130704813.us-central1.run.app/api/join', {
3365
+ method: 'POST',
3366
+ headers: { 'Content-Type': 'application/json' },
3367
+ body: JSON.stringify({ contribution: args.contribution || 0.3, key })
3368
+ });
3369
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
3370
+ const data = await res.json();
3371
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...data }, null, 2) }] };
3828
3372
  } catch (e) {
3829
3373
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3830
3374
  }
3831
3375
  }
3832
3376
 
3833
- case 'midstream_health': {
3377
+ case 'edge_balance': {
3834
3378
  try {
3835
- const brainUrl = process.env.BRAIN_URL || 'https://pi.ruv.io';
3836
- const brainKey = process.env.PI;
3837
- const hdrs = { 'Content-Type': 'application/json' };
3838
- if (brainKey) hdrs['Authorization'] = `Bearer ${brainKey}`;
3839
-
3840
- const [healthResp, midResp] = await Promise.all([
3841
- proxyFetch(`${brainUrl}/v1/health`, { headers: hdrs, signal: AbortSignal.timeout(15000) }).then(r => r.json()).catch(e => ({ error: e.message })),
3842
- proxyFetch(`${brainUrl}/v1/midstream`, { headers: hdrs, signal: AbortSignal.timeout(15000) }).then(r => r.json()).catch(e => ({ error: e.message })),
3843
- ]);
3844
-
3845
- return { content: [{ type: 'text', text: JSON.stringify({
3846
- success: true,
3847
- health: healthResp,
3848
- midstream: midResp
3849
- }, null, 2) }] };
3379
+ const key = args.key || process.env.PI || '';
3380
+ const res = await fetch(`https://edge-net-genesis-875130704813.us-central1.run.app/api/balance?key=${encodeURIComponent(key)}`);
3381
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
3382
+ const data = await res.json();
3383
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...data }, null, 2) }] };
3850
3384
  } catch (e) {
3851
3385
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3852
3386
  }
3853
3387
  }
3854
3388
 
3855
- // ── Edge Tool Handlers ───────────────────────────────────────────────
3856
- case 'edge_status':
3857
- case 'edge_join':
3858
- case 'edge_balance':
3859
3389
  case 'edge_tasks': {
3860
3390
  try {
3861
- const genesisUrl = process.env.EDGE_GENESIS_URL || 'https://edge-net-genesis-875130704813.us-central1.run.app';
3862
- const subCmd = name.replace('edge_', '');
3863
- let endpoint, method = 'GET', body;
3864
- switch (subCmd) {
3865
- case 'status': endpoint = '/status'; break;
3866
- case 'join': endpoint = '/join'; method = 'POST'; body = JSON.stringify({ contribution: args.contribution || 0.3, pi_key: process.env.PI }); break;
3867
- case 'balance': { const ps = process.env.PI ? require('crypto').createHash('shake256', { outputLength: 16 }).update(process.env.PI).digest('hex') : 'anonymous'; endpoint = `/balance/${ps}`; break; }
3868
- case 'tasks': endpoint = `/tasks?limit=${args.limit || 20}`; break;
3869
- }
3870
- const resp = await proxyFetch(`${genesisUrl}${endpoint}`, {
3871
- method,
3872
- headers: { 'Content-Type': 'application/json', ...(process.env.PI ? { 'Authorization': `Bearer ${process.env.PI}` } : {}) },
3873
- ...(body ? { body } : {})
3874
- });
3875
- if (!resp.ok) {
3876
- const errText = await resp.text().catch(() => resp.statusText);
3877
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: `${resp.status} ${errText}` }, null, 2) }], isError: true };
3878
- }
3879
- const data = await resp.json();
3391
+ const res = await fetch('https://edge-net-genesis-875130704813.us-central1.run.app/api/tasks');
3392
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
3393
+ const data = await res.json();
3880
3394
  return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...data }, null, 2) }] };
3881
3395
  } catch (e) {
3882
3396
  return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3883
3397
  }
3884
3398
  }
3885
3399
 
3886
- // ── Identity Tool Handlers ───────────────────────────────────────────
3400
+ // ── Identity Tool Handlers ──────────────────────────────────────────
3887
3401
  case 'identity_generate': {
3888
3402
  const crypto = require('crypto');
3889
3403
  const key = crypto.randomBytes(32).toString('hex');
3890
- const hash = crypto.createHash('shake256', { outputLength: 16 });
3891
- hash.update(key);
3892
- const pseudonym = hash.digest('hex');
3893
- return { content: [{ type: 'text', text: JSON.stringify({ success: true, pi_key: key, pseudonym, warning: 'Store this key securely. Set PI env var to use it.' }, null, 2) }] };
3404
+ const pseudonym = crypto.createHash('shake256', { outputLength: 16 }).update(key).digest('hex');
3405
+ const mcpToken = crypto.createHmac('sha256', key).update('mcp').digest('hex').slice(0, 32);
3406
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, key, pseudonym, mcp_token: mcpToken, instructions: 'Set PI env var: export PI=' + key }, null, 2) }] };
3894
3407
  }
3895
3408
 
3896
3409
  case 'identity_show': {
3897
- const piKey = process.env.PI;
3898
- if (!piKey) {
3899
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'No PI environment variable set. Run identity_generate first.' }, null, 2) }], isError: true };
3900
- }
3901
3410
  const crypto = require('crypto');
3902
- const hash = crypto.createHash('shake256', { outputLength: 16 });
3903
- hash.update(piKey);
3904
- const pseudonym = hash.digest('hex');
3905
- const mcpToken = crypto.createHmac('sha256', piKey).update('mcp').digest('hex').slice(0, 32);
3906
- return { content: [{ type: 'text', text: JSON.stringify({ success: true, pseudonym, mcp_token: mcpToken, key_prefix: piKey.slice(0, 8) + '...' }, null, 2) }] };
3411
+ const key = args.key || process.env.PI || '';
3412
+ if (!key) {
3413
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'No PI key found. Set PI env var or pass key argument' }, null, 2) }] };
3414
+ }
3415
+ const pseudonym = crypto.createHash('shake256', { outputLength: 16 }).update(key).digest('hex');
3416
+ const mcpToken = crypto.createHmac('sha256', key).update('mcp').digest('hex').slice(0, 32);
3417
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, pseudonym, mcp_token: mcpToken, key_prefix: key.slice(0, 8) + '...' }, null, 2) }] };
3907
3418
  }
3908
3419
 
3909
3420
  default:
@@ -3994,173 +3505,9 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
3994
3505
 
3995
3506
  // Start server
3996
3507
  async function main() {
3997
- const transportType = process.env.MCP_TRANSPORT || 'stdio';
3998
-
3999
- if (transportType === 'sse') {
4000
- const http = require('http');
4001
- const crypto = require('crypto');
4002
- const port = parseInt(process.env.MCP_PORT || '8080', 10);
4003
- const host = process.env.MCP_HOST || '0.0.0.0';
4004
-
4005
- // SSE MCP Transport Implementation
4006
- // MCP over SSE uses:
4007
- // GET /sse - SSE stream for server->client messages
4008
- // POST /message - client->server JSON-RPC messages
4009
-
4010
- const sessions = new Map();
4011
-
4012
- const httpServer = http.createServer(async (req, res) => {
4013
- // CORS headers
4014
- res.setHeader('Access-Control-Allow-Origin', '*');
4015
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
4016
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
4017
-
4018
- if (req.method === 'OPTIONS') {
4019
- res.writeHead(204);
4020
- res.end();
4021
- return;
4022
- }
4023
-
4024
- const url = new URL(req.url, `http://${req.headers.host}`);
4025
-
4026
- if (req.method === 'GET' && url.pathname === '/sse') {
4027
- // SSE endpoint - establish persistent connection
4028
- const sessionId = crypto.randomUUID();
4029
-
4030
- res.writeHead(200, {
4031
- 'Content-Type': 'text/event-stream',
4032
- 'Cache-Control': 'no-cache',
4033
- 'Connection': 'keep-alive',
4034
- });
4035
-
4036
- // Send endpoint event so client knows where to POST
4037
- const displayHost = host === '0.0.0.0' ? 'localhost' : host;
4038
- const messageUrl = `http://${displayHost}:${port}/message?sessionId=${sessionId}`;
4039
- res.write(`event: endpoint\ndata: ${messageUrl}\n\n`);
4040
-
4041
- // Store session
4042
- sessions.set(sessionId, {
4043
- res,
4044
- messageQueue: [],
4045
- });
4046
-
4047
- // Create a custom transport for this session
4048
- const sessionTransport = {
4049
- _onMessage: null,
4050
- _onClose: null,
4051
- _onError: null,
4052
- _started: false,
4053
-
4054
- async start() {
4055
- this._started = true;
4056
- },
4057
-
4058
- async close() {
4059
- sessions.delete(sessionId);
4060
- if (!res.writableEnded) {
4061
- res.end();
4062
- }
4063
- },
4064
-
4065
- async send(message) {
4066
- if (!res.writableEnded) {
4067
- res.write(`event: message\ndata: ${JSON.stringify(message)}\n\n`);
4068
- }
4069
- },
4070
-
4071
- set onmessage(handler) { this._onMessage = handler; },
4072
- get onmessage() { return this._onMessage; },
4073
- set onclose(handler) { this._onClose = handler; },
4074
- get onclose() { return this._onClose; },
4075
- set onerror(handler) { this._onError = handler; },
4076
- get onerror() { return this._onError; },
4077
- };
4078
-
4079
- sessions.get(sessionId).transport = sessionTransport;
4080
-
4081
- // Connect server to this transport
4082
- await server.connect(sessionTransport);
4083
-
4084
- // Process any queued messages
4085
- const session = sessions.get(sessionId);
4086
- if (session) {
4087
- for (const msg of session.messageQueue) {
4088
- if (sessionTransport._onMessage) {
4089
- sessionTransport._onMessage(msg);
4090
- }
4091
- }
4092
- session.messageQueue = [];
4093
- }
4094
-
4095
- // Handle disconnect
4096
- req.on('close', () => {
4097
- sessions.delete(sessionId);
4098
- if (sessionTransport._onClose) {
4099
- sessionTransport._onClose();
4100
- }
4101
- });
4102
-
4103
- } else if (req.method === 'POST' && url.pathname === '/message') {
4104
- // Message endpoint - receive client JSON-RPC messages
4105
- const sessionId = url.searchParams.get('sessionId');
4106
- const session = sessions.get(sessionId);
4107
-
4108
- if (!session) {
4109
- res.writeHead(404, { 'Content-Type': 'application/json' });
4110
- res.end(JSON.stringify({ error: 'Session not found' }));
4111
- return;
4112
- }
4113
-
4114
- let body = '';
4115
- req.on('data', chunk => { body += chunk; });
4116
- req.on('end', () => {
4117
- try {
4118
- const message = JSON.parse(body);
4119
-
4120
- if (session.transport && session.transport._onMessage) {
4121
- session.transport._onMessage(message);
4122
- } else {
4123
- session.messageQueue.push(message);
4124
- }
4125
-
4126
- res.writeHead(202, { 'Content-Type': 'application/json' });
4127
- res.end(JSON.stringify({ status: 'accepted' }));
4128
- } catch (e) {
4129
- res.writeHead(400, { 'Content-Type': 'application/json' });
4130
- res.end(JSON.stringify({ error: 'Invalid JSON' }));
4131
- }
4132
- });
4133
-
4134
- } else if (req.method === 'GET' && url.pathname === '/health') {
4135
- res.writeHead(200, { 'Content-Type': 'application/json' });
4136
- res.end(JSON.stringify({
4137
- status: 'ok',
4138
- transport: 'sse',
4139
- sessions: sessions.size,
4140
- tools: 91,
4141
- version: '0.2.13'
4142
- }));
4143
-
4144
- } else {
4145
- res.writeHead(404, { 'Content-Type': 'application/json' });
4146
- res.end(JSON.stringify({ error: 'Not found. Use GET /sse for SSE stream, POST /message for JSON-RPC, GET /health for status.' }));
4147
- }
4148
- });
4149
-
4150
- httpServer.listen(port, host, () => {
4151
- const displayHost = host === '0.0.0.0' ? 'localhost' : host;
4152
- console.error(`RuVector MCP server running on SSE at http://${host}:${port}`);
4153
- console.error(` SSE endpoint: http://${displayHost}:${port}/sse`);
4154
- console.error(` Message endpoint: http://${displayHost}:${port}/message`);
4155
- console.error(` Health check: http://${displayHost}:${port}/health`);
4156
- });
4157
-
4158
- } else {
4159
- // Default: stdio transport
4160
- const transport = new StdioServerTransport();
4161
- await server.connect(transport);
4162
- console.error('RuVector MCP server running on stdio');
4163
- }
3508
+ const transport = new StdioServerTransport();
3509
+ await server.connect(transport);
3510
+ console.error('RuVector MCP server running on stdio');
4164
3511
  }
4165
3512
 
4166
3513
  main().catch(console.error);