ruvector 0.2.0 → 0.2.2

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 (3) hide show
  1. package/bin/cli.js +1116 -663
  2. package/bin/mcp-server.js +516 -26
  3. package/package.json +7 -7
package/bin/mcp-server.js CHANGED
@@ -363,7 +363,7 @@ class Intelligence {
363
363
  const server = new Server(
364
364
  {
365
365
  name: 'ruvector',
366
- version: '0.1.58',
366
+ version: '0.2.2',
367
367
  },
368
368
  {
369
369
  capabilities: {
@@ -1224,11 +1224,12 @@ const TOOLS = [
1224
1224
  },
1225
1225
  {
1226
1226
  name: 'rvf_examples',
1227
- description: 'List available example .rvf files with download URLs from the ruvector repository',
1227
+ description: 'List available example .rvf files with download URLs. Supports filtering by name, description, or category.',
1228
1228
  inputSchema: {
1229
1229
  type: 'object',
1230
1230
  properties: {
1231
- filter: { type: 'string', description: 'Filter examples by name or description substring' }
1231
+ filter: { type: 'string', description: 'Filter examples by name or description substring' },
1232
+ category: { type: 'string', description: 'Filter by category (core, ai, security, compute, lineage, industry, network, integration)' }
1232
1233
  },
1233
1234
  required: []
1234
1235
  }
@@ -1269,6 +1270,185 @@ const TOOLS = [
1269
1270
  },
1270
1271
  required: ['query']
1271
1272
  }
1273
+ },
1274
+ // ── Brain Tools (11) ── Shared intelligence via @ruvector/pi-brain ──
1275
+ {
1276
+ name: 'brain_search',
1277
+ description: 'Semantic search across shared brain knowledge',
1278
+ inputSchema: {
1279
+ type: 'object',
1280
+ properties: {
1281
+ query: { type: 'string', description: 'Search query' },
1282
+ category: { type: 'string', description: 'Filter by category (pattern, solution, architecture, convention, security, performance, tooling)' },
1283
+ limit: { type: 'number', description: 'Max results (default 10)' }
1284
+ },
1285
+ required: ['query']
1286
+ }
1287
+ },
1288
+ {
1289
+ name: 'brain_share',
1290
+ description: 'Share a learning or pattern with the collective brain',
1291
+ inputSchema: {
1292
+ type: 'object',
1293
+ properties: {
1294
+ title: { type: 'string', description: 'Title of the knowledge entry' },
1295
+ content: { type: 'string', description: 'Content/description of the knowledge' },
1296
+ category: { type: 'string', description: 'Category (pattern, solution, architecture, convention, security, performance, tooling)' },
1297
+ tags: { type: 'string', description: 'Comma-separated tags' },
1298
+ code_snippet: { type: 'string', description: 'Optional code snippet' }
1299
+ },
1300
+ required: ['title', 'content', 'category']
1301
+ }
1302
+ },
1303
+ {
1304
+ name: 'brain_get',
1305
+ description: 'Retrieve a specific memory by ID with full provenance',
1306
+ inputSchema: {
1307
+ type: 'object',
1308
+ properties: {
1309
+ id: { type: 'string', description: 'Memory ID' }
1310
+ },
1311
+ required: ['id']
1312
+ }
1313
+ },
1314
+ {
1315
+ name: 'brain_vote',
1316
+ description: 'Quality-gate a memory with an up or down vote',
1317
+ inputSchema: {
1318
+ type: 'object',
1319
+ properties: {
1320
+ id: { type: 'string', description: 'Memory ID' },
1321
+ direction: { type: 'string', description: 'Vote direction: up or down' }
1322
+ },
1323
+ required: ['id', 'direction']
1324
+ }
1325
+ },
1326
+ {
1327
+ name: 'brain_list',
1328
+ description: 'List recent shared memories filtered by category or quality',
1329
+ inputSchema: {
1330
+ type: 'object',
1331
+ properties: {
1332
+ category: { type: 'string', description: 'Filter by category' },
1333
+ limit: { type: 'number', description: 'Max results (default 20)' }
1334
+ }
1335
+ }
1336
+ },
1337
+ {
1338
+ name: 'brain_delete',
1339
+ description: 'Delete your own contribution from the shared brain',
1340
+ inputSchema: {
1341
+ type: 'object',
1342
+ properties: {
1343
+ id: { type: 'string', description: 'Memory ID to delete' }
1344
+ },
1345
+ required: ['id']
1346
+ }
1347
+ },
1348
+ {
1349
+ name: 'brain_status',
1350
+ description: 'Get shared brain system health: counts, drift, quality, graph topology',
1351
+ inputSchema: {
1352
+ type: 'object',
1353
+ properties: {}
1354
+ }
1355
+ },
1356
+ {
1357
+ name: 'brain_drift',
1358
+ description: 'Check if shared knowledge has drifted from local state',
1359
+ inputSchema: {
1360
+ type: 'object',
1361
+ properties: {
1362
+ domain: { type: 'string', description: 'Domain to check drift for' }
1363
+ }
1364
+ }
1365
+ },
1366
+ {
1367
+ name: 'brain_partition',
1368
+ description: 'Get knowledge partitioned by mincut topology into clusters',
1369
+ inputSchema: {
1370
+ type: 'object',
1371
+ properties: {
1372
+ domain: { type: 'string', description: 'Domain to partition' },
1373
+ min_cluster_size: { type: 'number', description: 'Minimum cluster size (default 3)' }
1374
+ }
1375
+ }
1376
+ },
1377
+ {
1378
+ name: 'brain_transfer',
1379
+ description: 'Apply learned priors from one knowledge domain to another',
1380
+ inputSchema: {
1381
+ type: 'object',
1382
+ properties: {
1383
+ source_domain: { type: 'string', description: 'Source domain to transfer from' },
1384
+ target_domain: { type: 'string', description: 'Target domain to transfer to' }
1385
+ },
1386
+ required: ['source_domain', 'target_domain']
1387
+ }
1388
+ },
1389
+ {
1390
+ name: 'brain_sync',
1391
+ description: 'Synchronize LoRA weights between local and shared brain',
1392
+ inputSchema: {
1393
+ type: 'object',
1394
+ properties: {
1395
+ direction: { type: 'string', description: 'Sync direction: pull, push, or both (default both)' }
1396
+ }
1397
+ }
1398
+ },
1399
+ // ── Edge Tools (4) ── Distributed compute via @ruvector/edge-net ──
1400
+ {
1401
+ name: 'edge_status',
1402
+ description: 'Get edge compute network status (genesis, relay, nodes, rUv supply)',
1403
+ inputSchema: {
1404
+ type: 'object',
1405
+ properties: {}
1406
+ }
1407
+ },
1408
+ {
1409
+ name: 'edge_join',
1410
+ description: 'Join the edge compute network as a compute node',
1411
+ inputSchema: {
1412
+ type: 'object',
1413
+ properties: {
1414
+ contribution: { type: 'number', description: 'Contribution level 0.0-1.0 (default 0.3)' }
1415
+ }
1416
+ }
1417
+ },
1418
+ {
1419
+ name: 'edge_balance',
1420
+ description: 'Check rUv credit balance for current identity',
1421
+ inputSchema: {
1422
+ type: 'object',
1423
+ properties: {}
1424
+ }
1425
+ },
1426
+ {
1427
+ name: 'edge_tasks',
1428
+ description: 'List available distributed compute tasks on the edge network',
1429
+ inputSchema: {
1430
+ type: 'object',
1431
+ properties: {
1432
+ limit: { type: 'number', description: 'Max tasks to return (default 20)' }
1433
+ }
1434
+ }
1435
+ },
1436
+ // ── Identity Tools (2) ── Pi key management ──
1437
+ {
1438
+ name: 'identity_generate',
1439
+ description: 'Generate a new pi key and derive pseudonym',
1440
+ inputSchema: {
1441
+ type: 'object',
1442
+ properties: {}
1443
+ }
1444
+ },
1445
+ {
1446
+ name: 'identity_show',
1447
+ description: 'Show current pi key pseudonym and derived identities',
1448
+ inputSchema: {
1449
+ type: 'object',
1450
+ properties: {}
1451
+ }
1272
1452
  }
1273
1453
  ];
1274
1454
 
@@ -2843,31 +3023,85 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2843
3023
  }
2844
3024
 
2845
3025
  case 'rvf_examples': {
2846
- const BASE_URL = 'https://raw.githubusercontent.com/ruvnet/ruvector/main/examples/rvf/output';
2847
- const examples = [
2848
- { name: 'basic_store', size: '152 KB', desc: '1,000 vectors, dim 128' },
2849
- { name: 'semantic_search', size: '755 KB', desc: 'Semantic search with HNSW' },
2850
- { name: 'rag_pipeline', size: '303 KB', desc: 'RAG pipeline embeddings' },
2851
- { name: 'agent_memory', size: '32 KB', desc: 'AI agent episodic memory' },
2852
- { name: 'swarm_knowledge', size: '86 KB', desc: 'Multi-agent knowledge base' },
2853
- { name: 'self_booting', size: '31 KB', desc: 'Self-booting with kernel' },
2854
- { name: 'ebpf_accelerator', size: '153 KB', desc: 'eBPF distance accelerator' },
2855
- { name: 'tee_attestation', size: '102 KB', desc: 'TEE attestation + witnesses' },
2856
- { name: 'lineage_parent', size: '52 KB', desc: 'COW parent file' },
2857
- { name: 'lineage_child', size: '26 KB', desc: 'COW child (derived)' },
2858
- { name: 'claude_code_appliance', size: '17 KB', desc: 'Claude Code appliance' },
2859
- { name: 'progressive_index', size: '2.5 MB', desc: 'Large-scale HNSW index' },
2860
- ];
2861
- let filtered = examples;
3026
+ const os = require('os');
3027
+ const GCS_MANIFEST = 'https://storage.googleapis.com/ruvector-examples/manifest.json';
3028
+ const GITHUB_RAW = 'https://raw.githubusercontent.com/ruvnet/ruvector/main/examples/rvf/output';
3029
+ const cacheDir = path.join(os.homedir(), '.ruvector', 'examples');
3030
+ const manifestPath = path.join(cacheDir, 'manifest.json');
3031
+
3032
+ let manifest;
3033
+ // Try cache first
3034
+ if (fs.existsSync(manifestPath)) {
3035
+ try {
3036
+ const stat = fs.statSync(manifestPath);
3037
+ if (Date.now() - stat.mtimeMs < 3600000) {
3038
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
3039
+ }
3040
+ } catch {}
3041
+ }
3042
+
3043
+ // Fetch from GCS if no fresh cache
3044
+ if (!manifest) {
3045
+ try {
3046
+ const resp = await fetch(GCS_MANIFEST);
3047
+ if (resp.ok) {
3048
+ manifest = await resp.json();
3049
+ try {
3050
+ fs.mkdirSync(cacheDir, { recursive: true });
3051
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
3052
+ } catch {}
3053
+ }
3054
+ } catch {}
3055
+ }
3056
+
3057
+ // Fallback to hardcoded
3058
+ if (!manifest) {
3059
+ manifest = {
3060
+ version: 'builtin',
3061
+ base_url: GITHUB_RAW,
3062
+ examples: [
3063
+ { name: 'basic_store', size_human: '152 KB', description: '1,000 vectors, dim 128', category: 'core' },
3064
+ { name: 'semantic_search', size_human: '755 KB', description: 'Semantic search with HNSW', category: 'core' },
3065
+ { name: 'rag_pipeline', size_human: '303 KB', description: 'RAG pipeline embeddings', category: 'core' },
3066
+ { name: 'agent_memory', size_human: '32 KB', description: 'AI agent episodic memory', category: 'ai' },
3067
+ { name: 'swarm_knowledge', size_human: '86 KB', description: 'Multi-agent knowledge base', category: 'ai' },
3068
+ { name: 'self_booting', size_human: '31 KB', description: 'Self-booting with kernel', category: 'compute' },
3069
+ { name: 'ebpf_accelerator', size_human: '153 KB', description: 'eBPF distance accelerator', category: 'compute' },
3070
+ { name: 'tee_attestation', size_human: '102 KB', description: 'TEE attestation + witnesses', category: 'security' },
3071
+ { name: 'claude_code_appliance', size_human: '17 KB', description: 'Claude Code appliance', category: 'integration' },
3072
+ { name: 'lineage_parent', size_human: '52 KB', description: 'COW parent file', category: 'lineage' },
3073
+ { name: 'financial_signals', size_human: '202 KB', description: 'Financial signals', category: 'industry' },
3074
+ { name: 'progressive_index', size_human: '2.5 MB', description: 'Large-scale HNSW index', category: 'core' },
3075
+ ]
3076
+ };
3077
+ }
3078
+
3079
+ let examples = manifest.examples || [];
3080
+ const baseUrl = manifest.base_url || GITHUB_RAW;
3081
+
2862
3082
  if (args.filter) {
2863
3083
  const f = args.filter.toLowerCase();
2864
- filtered = examples.filter(e => e.name.includes(f) || e.desc.toLowerCase().includes(f));
3084
+ examples = examples.filter(e =>
3085
+ e.name.includes(f) ||
3086
+ (e.description || '').toLowerCase().includes(f) ||
3087
+ (e.category || '').includes(f)
3088
+ );
3089
+ }
3090
+
3091
+ if (args.category) {
3092
+ examples = examples.filter(e => e.category === args.category);
2865
3093
  }
3094
+
2866
3095
  return { content: [{ type: 'text', text: JSON.stringify({
2867
3096
  success: true,
2868
- total: 45,
2869
- shown: filtered.length,
2870
- examples: filtered.map(e => ({ ...e, url: `${BASE_URL}/${e.name}.rvf` })),
3097
+ version: manifest.version,
3098
+ total: (manifest.examples || []).length,
3099
+ shown: examples.length,
3100
+ examples: examples.map(e => ({
3101
+ ...e,
3102
+ url: `${baseUrl}/${e.name}.rvf`
3103
+ })),
3104
+ categories: manifest.categories || {},
2871
3105
  catalog: 'https://github.com/ruvnet/ruvector/tree/main/examples/rvf/output'
2872
3106
  }, null, 2) }] };
2873
3107
  }
@@ -2963,6 +3197,98 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2963
3197
  }
2964
3198
  }
2965
3199
 
3200
+ // ── Brain Tool Handlers ──────────────────────────────────────────────
3201
+ case 'brain_search':
3202
+ case 'brain_share':
3203
+ case 'brain_get':
3204
+ case 'brain_vote':
3205
+ case 'brain_list':
3206
+ case 'brain_delete':
3207
+ case 'brain_status':
3208
+ case 'brain_drift':
3209
+ case 'brain_partition':
3210
+ case 'brain_transfer':
3211
+ case 'brain_sync': {
3212
+ try {
3213
+ const { PiBrainClient } = require('@ruvector/pi-brain');
3214
+ const client = new PiBrainClient({
3215
+ url: process.env.BRAIN_URL || 'https://pi.ruv.io',
3216
+ key: process.env.PI
3217
+ });
3218
+ const subCmd = name.replace('brain_', '');
3219
+ let result;
3220
+ switch (subCmd) {
3221
+ case 'search': result = await client.search(args.query, { category: args.category, limit: args.limit || 10 }); break;
3222
+ case 'share': result = await client.share({ title: args.title, content: args.content, category: args.category, tags: args.tags ? args.tags.split(',').map(t => t.trim()) : [], code_snippet: args.code_snippet }); break;
3223
+ case 'get': result = await client.get(args.id); break;
3224
+ case 'vote': result = await client.vote(args.id, args.direction); break;
3225
+ case 'list': result = await client.list({ category: args.category, limit: args.limit || 20 }); break;
3226
+ case 'delete': result = await client.delete(args.id); break;
3227
+ case 'status': result = await client.status(); break;
3228
+ case 'drift': result = await client.drift({ domain: args.domain }); break;
3229
+ case 'partition': result = await client.partition({ domain: args.domain, min_cluster_size: args.min_cluster_size }); break;
3230
+ case 'transfer': result = await client.transfer(args.source_domain, args.target_domain); break;
3231
+ case 'sync': result = await client.sync(args.direction || 'both'); break;
3232
+ }
3233
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }, null, 2) }] };
3234
+ } catch (e) {
3235
+ if (e.code === 'MODULE_NOT_FOUND') {
3236
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Brain tools require @ruvector/pi-brain. Install with: npm install @ruvector/pi-brain' }, null, 2) }], isError: true };
3237
+ }
3238
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3239
+ }
3240
+ }
3241
+
3242
+ // ── Edge Tool Handlers ───────────────────────────────────────────────
3243
+ case 'edge_status':
3244
+ case 'edge_join':
3245
+ case 'edge_balance':
3246
+ case 'edge_tasks': {
3247
+ try {
3248
+ const genesisUrl = process.env.EDGE_GENESIS_URL || 'https://edge-net-genesis-875130704813.us-central1.run.app';
3249
+ const subCmd = name.replace('edge_', '');
3250
+ let endpoint, method = 'GET', body;
3251
+ switch (subCmd) {
3252
+ case 'status': endpoint = '/status'; break;
3253
+ case 'join': endpoint = '/join'; method = 'POST'; body = JSON.stringify({ contribution: args.contribution || 0.3, pi_key: process.env.PI }); break;
3254
+ case 'balance': endpoint = `/balance/${process.env.PI || 'anonymous'}`; break;
3255
+ case 'tasks': endpoint = `/tasks?limit=${args.limit || 20}`; break;
3256
+ }
3257
+ const resp = await fetch(`${genesisUrl}${endpoint}`, {
3258
+ method,
3259
+ headers: { 'Content-Type': 'application/json', ...(process.env.PI ? { 'Authorization': `Bearer ${process.env.PI}` } : {}) },
3260
+ ...(body ? { body } : {})
3261
+ });
3262
+ const data = await resp.json();
3263
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...data }, null, 2) }] };
3264
+ } catch (e) {
3265
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true };
3266
+ }
3267
+ }
3268
+
3269
+ // ── Identity Tool Handlers ───────────────────────────────────────────
3270
+ case 'identity_generate': {
3271
+ const crypto = require('crypto');
3272
+ const key = crypto.randomBytes(32).toString('hex');
3273
+ const hash = crypto.createHash('shake256', { outputLength: 16 });
3274
+ hash.update(key);
3275
+ const pseudonym = hash.digest('hex');
3276
+ 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) }] };
3277
+ }
3278
+
3279
+ case 'identity_show': {
3280
+ const piKey = process.env.PI;
3281
+ if (!piKey) {
3282
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'No PI environment variable set. Run identity_generate first.' }, null, 2) }], isError: true };
3283
+ }
3284
+ const crypto = require('crypto');
3285
+ const hash = crypto.createHash('shake256', { outputLength: 16 });
3286
+ hash.update(piKey);
3287
+ const pseudonym = hash.digest('hex');
3288
+ const mcpToken = crypto.createHmac('sha256', piKey).update('mcp').digest('hex').slice(0, 32);
3289
+ return { content: [{ type: 'text', text: JSON.stringify({ success: true, pseudonym, mcp_token: mcpToken, key_prefix: piKey.slice(0, 8) + '...' }, null, 2) }] };
3290
+ }
3291
+
2966
3292
  default:
2967
3293
  return {
2968
3294
  content: [{
@@ -3051,9 +3377,173 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
3051
3377
 
3052
3378
  // Start server
3053
3379
  async function main() {
3054
- const transport = new StdioServerTransport();
3055
- await server.connect(transport);
3056
- console.error('RuVector MCP server running on stdio');
3380
+ const transportType = process.env.MCP_TRANSPORT || 'stdio';
3381
+
3382
+ if (transportType === 'sse') {
3383
+ const http = require('http');
3384
+ const crypto = require('crypto');
3385
+ const port = parseInt(process.env.MCP_PORT || '8080', 10);
3386
+ const host = process.env.MCP_HOST || '0.0.0.0';
3387
+
3388
+ // SSE MCP Transport Implementation
3389
+ // MCP over SSE uses:
3390
+ // GET /sse - SSE stream for server->client messages
3391
+ // POST /message - client->server JSON-RPC messages
3392
+
3393
+ const sessions = new Map();
3394
+
3395
+ const httpServer = http.createServer(async (req, res) => {
3396
+ // CORS headers
3397
+ res.setHeader('Access-Control-Allow-Origin', '*');
3398
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
3399
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
3400
+
3401
+ if (req.method === 'OPTIONS') {
3402
+ res.writeHead(204);
3403
+ res.end();
3404
+ return;
3405
+ }
3406
+
3407
+ const url = new URL(req.url, `http://${req.headers.host}`);
3408
+
3409
+ if (req.method === 'GET' && url.pathname === '/sse') {
3410
+ // SSE endpoint - establish persistent connection
3411
+ const sessionId = crypto.randomUUID();
3412
+
3413
+ res.writeHead(200, {
3414
+ 'Content-Type': 'text/event-stream',
3415
+ 'Cache-Control': 'no-cache',
3416
+ 'Connection': 'keep-alive',
3417
+ });
3418
+
3419
+ // Send endpoint event so client knows where to POST
3420
+ const displayHost = host === '0.0.0.0' ? 'localhost' : host;
3421
+ const messageUrl = `http://${displayHost}:${port}/message?sessionId=${sessionId}`;
3422
+ res.write(`event: endpoint\ndata: ${messageUrl}\n\n`);
3423
+
3424
+ // Store session
3425
+ sessions.set(sessionId, {
3426
+ res,
3427
+ messageQueue: [],
3428
+ });
3429
+
3430
+ // Create a custom transport for this session
3431
+ const sessionTransport = {
3432
+ _onMessage: null,
3433
+ _onClose: null,
3434
+ _onError: null,
3435
+ _started: false,
3436
+
3437
+ async start() {
3438
+ this._started = true;
3439
+ },
3440
+
3441
+ async close() {
3442
+ sessions.delete(sessionId);
3443
+ if (!res.writableEnded) {
3444
+ res.end();
3445
+ }
3446
+ },
3447
+
3448
+ async send(message) {
3449
+ if (!res.writableEnded) {
3450
+ res.write(`event: message\ndata: ${JSON.stringify(message)}\n\n`);
3451
+ }
3452
+ },
3453
+
3454
+ set onmessage(handler) { this._onMessage = handler; },
3455
+ get onmessage() { return this._onMessage; },
3456
+ set onclose(handler) { this._onClose = handler; },
3457
+ get onclose() { return this._onClose; },
3458
+ set onerror(handler) { this._onError = handler; },
3459
+ get onerror() { return this._onError; },
3460
+ };
3461
+
3462
+ sessions.get(sessionId).transport = sessionTransport;
3463
+
3464
+ // Connect server to this transport
3465
+ await server.connect(sessionTransport);
3466
+
3467
+ // Process any queued messages
3468
+ const session = sessions.get(sessionId);
3469
+ if (session) {
3470
+ for (const msg of session.messageQueue) {
3471
+ if (sessionTransport._onMessage) {
3472
+ sessionTransport._onMessage(msg);
3473
+ }
3474
+ }
3475
+ session.messageQueue = [];
3476
+ }
3477
+
3478
+ // Handle disconnect
3479
+ req.on('close', () => {
3480
+ sessions.delete(sessionId);
3481
+ if (sessionTransport._onClose) {
3482
+ sessionTransport._onClose();
3483
+ }
3484
+ });
3485
+
3486
+ } else if (req.method === 'POST' && url.pathname === '/message') {
3487
+ // Message endpoint - receive client JSON-RPC messages
3488
+ const sessionId = url.searchParams.get('sessionId');
3489
+ const session = sessions.get(sessionId);
3490
+
3491
+ if (!session) {
3492
+ res.writeHead(404, { 'Content-Type': 'application/json' });
3493
+ res.end(JSON.stringify({ error: 'Session not found' }));
3494
+ return;
3495
+ }
3496
+
3497
+ let body = '';
3498
+ req.on('data', chunk => { body += chunk; });
3499
+ req.on('end', () => {
3500
+ try {
3501
+ const message = JSON.parse(body);
3502
+
3503
+ if (session.transport && session.transport._onMessage) {
3504
+ session.transport._onMessage(message);
3505
+ } else {
3506
+ session.messageQueue.push(message);
3507
+ }
3508
+
3509
+ res.writeHead(202, { 'Content-Type': 'application/json' });
3510
+ res.end(JSON.stringify({ status: 'accepted' }));
3511
+ } catch (e) {
3512
+ res.writeHead(400, { 'Content-Type': 'application/json' });
3513
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
3514
+ }
3515
+ });
3516
+
3517
+ } else if (req.method === 'GET' && url.pathname === '/health') {
3518
+ res.writeHead(200, { 'Content-Type': 'application/json' });
3519
+ res.end(JSON.stringify({
3520
+ status: 'ok',
3521
+ transport: 'sse',
3522
+ sessions: sessions.size,
3523
+ tools: 91,
3524
+ version: '0.2.2'
3525
+ }));
3526
+
3527
+ } else {
3528
+ res.writeHead(404, { 'Content-Type': 'application/json' });
3529
+ res.end(JSON.stringify({ error: 'Not found. Use GET /sse for SSE stream, POST /message for JSON-RPC, GET /health for status.' }));
3530
+ }
3531
+ });
3532
+
3533
+ httpServer.listen(port, host, () => {
3534
+ const displayHost = host === '0.0.0.0' ? 'localhost' : host;
3535
+ console.error(`RuVector MCP server running on SSE at http://${host}:${port}`);
3536
+ console.error(` SSE endpoint: http://${displayHost}:${port}/sse`);
3537
+ console.error(` Message endpoint: http://${displayHost}:${port}/message`);
3538
+ console.error(` Health check: http://${displayHost}:${port}/health`);
3539
+ });
3540
+
3541
+ } else {
3542
+ // Default: stdio transport
3543
+ const transport = new StdioServerTransport();
3544
+ await server.connect(transport);
3545
+ console.error('RuVector MCP server running on stdio');
3546
+ }
3057
3547
  }
3058
3548
 
3059
3549
  main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvector",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "High-performance vector database for Node.js with automatic native/WASM fallback",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -79,12 +79,6 @@
79
79
  "@types/node": "^20.10.5",
80
80
  "typescript": "^5.3.3"
81
81
  },
82
- "files": [
83
- "bin/",
84
- "dist/",
85
- "README.md",
86
- "LICENSE"
87
- ],
88
82
  "peerDependencies": {
89
83
  "@ruvector/pi-brain": ">=0.1.0",
90
84
  "@ruvector/ruvllm": ">=2.0.0",
@@ -95,6 +89,12 @@
95
89
  "@ruvector/ruvllm": { "optional": true },
96
90
  "@ruvector/router": { "optional": true }
97
91
  },
92
+ "files": [
93
+ "bin/",
94
+ "dist/",
95
+ "README.md",
96
+ "LICENSE"
97
+ ],
98
98
  "engines": {
99
99
  "node": ">=18.0.0"
100
100
  }