UNPKG

brain-mcp

Version:

Brain MCP Server - Semantic knowledge base access for Claude Code via Model Context Protocol. Provides intelligent search and navigation of files from multiple locations through native MCP tools.

304 lines 13.5 kB
"use strict"; /** * Output formatters optimized for LLM consumption */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.LLMFormatter = void 0; const path = __importStar(require("path")); class LLMFormatter { formatOverview(graph) { const totalNotes = graph.nodes.size; if (totalNotes === 0) { return 'KNOWLEDGE BASE: Empty'; } const output = []; output.push('=== KNOWLEDGE BASE OVERVIEW ==='); // Basic stats const validLinks = Array.from(graph.nodes.values()).reduce((sum, node) => sum + node.note.outgoingLinks.filter(l => !l.isBroken).length, 0); output.push(`${totalNotes} notes | ${validLinks} links | Vector search enabled\n`); // Topic clusters by directory structure const topicClusters = new Map(); for (const node of graph.nodes.values()) { const dir = path.dirname(node.note.relativePath); const topic = dir === '.' ? 'root' : dir.split('/')[0]; if (!topicClusters.has(topic)) { topicClusters.set(topic, { count: 0, keyNotes: [] }); } const cluster = topicClusters.get(topic); cluster.count++; // Track highly connected notes (>10 connections) const connections = node.inDegree + node.outDegree; if (connections > 10) { cluster.keyNotes.push(node.note.title); } } // Display topic areas output.push('TOPIC AREAS:'); for (const [topic, data] of Array.from(topicClusters.entries()).sort((a, b) => b[1].count - a[1].count)) { const keyNotesSummary = data.keyNotes.length > 0 ? ` (key: ${data.keyNotes.slice(0, 2).join(', ')}${data.keyNotes.length > 2 ? '...' : ''})` : ''; output.push(`• ${topic}: ${data.count} notes${keyNotesSummary}`); } // Top hub notes const topHubs = Array.from(graph.nodes.values()) .sort((a, b) => (b.inDegree + b.outDegree) - (a.inDegree + a.outDegree)) .slice(0, 3); if (topHubs.length > 0) { output.push('\nKEY HUBS:'); for (const node of topHubs) { const connections = node.inDegree + node.outDegree; output.push(`• ${node.note.title} (${connections} connections)`); } } // Recent activity const now = new Date(); const recentNotes = Array.from(graph.nodes.values()) .filter(node => { if (!node.note.lastModified) return false; const daysAgo = (now.getTime() - node.note.lastModified.getTime()) / (1000 * 60 * 60 * 24); return daysAgo <= 3; }) .sort((a, b) => (b.note.lastModified?.getTime() || 0) - (a.note.lastModified?.getTime() || 0)) .slice(0, 3); if (recentNotes.length > 0) { output.push('\nRECENT UPDATES:'); for (const node of recentNotes) { const daysAgo = Math.floor((now.getTime() - (node.note.lastModified?.getTime() || 0)) / (1000 * 60 * 60 * 24)); const timeStr = daysAgo === 0 ? 'today' : `${daysAgo}d ago`; output.push(`• ${node.note.title} (${timeStr})`); } } // Usage instructions output.push('\nROUTING:'); output.push('• brain check "<query>" - Check if knowledge base is relevant'); output.push('• brain search "<query>" - Traditional search if relevant'); return output.join('\n'); } formatLs(graph, dirPath = '') { const output = []; // Group notes by directory const dirs = new Map(); const files = new Map(); for (const node of graph.nodes.values()) { let relativePath = node.note.relativePath; // Filter by requested path if (dirPath) { if (!relativePath.startsWith(dirPath)) { continue; } // Adjust relative path relativePath = path.relative(dirPath, relativePath); } const pathParts = relativePath.split(path.sep); if (pathParts.length === 1) { // File in current directory files.set(relativePath, node); } else { // File in subdirectory const subdir = pathParts[0]; if (!dirs.has(subdir)) { dirs.set(subdir, []); } dirs.get(subdir).push(node); } } // Display path header const displayPath = dirPath ? `/${dirPath}` : '/'; output.push(displayPath); // Display subdirectories for (const [dirname, dirNodes] of Array.from(dirs.entries()).sort()) { const noteCount = dirNodes.length; output.push(`├── ${dirname}/ (${noteCount} notes)`); // Show sample files in directory const sortedNodes = dirNodes.sort((a, b) => a.note.title.localeCompare(b.note.title)); for (let i = 0; i < Math.min(3, sortedNodes.length); i++) { const node = sortedNodes[i]; const filename = path.basename(node.note.relativePath); output.push(`│ ├── ${filename} [→${node.outDegree}${node.inDegree}]`); } if (dirNodes.length > 3) { output.push(`│ └── ... and ${dirNodes.length - 3} more`); } } // Display files in current directory for (const [filename, node] of Array.from(files.entries()).sort()) { output.push(`└── ${filename} [→${node.outDegree}${node.inDegree}]`); } if (dirs.size === 0 && files.size === 0) { output.push('(empty)'); } output.push('\n[→X ←Y] means X outgoing links, Y incoming links'); return output.join('\n'); } formatNoteRead(node, content) { const output = []; // Header with metadata output.push(`=== ${node.note.relativePath} ===`); output.push(`Location: ${node.note.path}`); if (node.note.lastModified) { output.push(`Modified: ${node.note.lastModified.toISOString().replace('T', ' ').split('.')[0]}`); } if (node.note.tags.size > 0) { const tagsStr = Array.from(node.note.tags).sort().map(tag => `#${tag}`).join(' '); output.push(`Tags: ${tagsStr}`); } output.push(`Words: ${node.note.wordCount}`); // Incoming links if (node.incomingLinks.length > 0) { output.push(`\nINCOMING LINKS (${node.incomingLinks.length}):`); for (let i = 0; i < Math.min(10, node.incomingLinks.length); i++) { const link = node.incomingLinks[i]; const sourcePath = path.basename(link.sourcePath); const truncatedContext = link.context.slice(0, 50) + '...'; output.push(`← "${sourcePath}" ("${truncatedContext}")`); } if (node.incomingLinks.length > 10) { output.push(` ... and ${node.incomingLinks.length - 10} more`); } } // Outgoing links const validOutgoing = node.note.outgoingLinks.filter(l => !l.isBroken); if (validOutgoing.length > 0) { output.push(`\nOUTGOING LINKS (${validOutgoing.length}):`); for (let i = 0; i < Math.min(10, validOutgoing.length); i++) { const link = validOutgoing[i]; if (link.targetPath) { const targetPath = path.basename(link.targetPath); const truncatedContext = link.context.slice(0, 50) + '...'; output.push(`→ "${targetPath}" ("${truncatedContext}")`); } } } // Content if (content !== undefined) { output.push('\nCONTENT:'); output.push('-'.repeat(40)); output.push(content); } return output.join('\n'); } formatSemanticSearchResults(results, graph) { if (results.length === 0) { return 'No relevant content found. Try lowering the similarity threshold with the -t option.'; } const output = []; output.push('=== SEMANTIC SEARCH RESULTS ===\n'); // Group results by note to avoid duplicates const resultsByNote = new Map(); for (const result of results) { if (!resultsByNote.has(result.notePath)) { resultsByNote.set(result.notePath, []); } resultsByNote.get(result.notePath).push(result); } let resultCount = 0; for (const [notePath, noteResults] of resultsByNote.entries()) { if (resultCount >= results.length) break; const node = graph.nodes.get(notePath); const noteTitle = node ? node.note.title : path.basename(notePath); const relativePath = node ? node.note.relativePath : notePath; // Sort by similarity and take best chunk const bestResult = noteResults.sort((a, b) => b.similarity - a.similarity)[0]; output.push(`📄 ${noteTitle}`); output.push(` Path: ${relativePath}`); output.push(` Similarity: ${(bestResult.similarity * 100).toFixed(1)}%`); if (bestResult.headingContext.length > 0) { output.push(` Section: ${bestResult.headingContext.join(' > ')}`); } output.push(` Content: ${bestResult.snippet}`); // Show additional chunks if they're significantly relevant const additionalChunks = noteResults.slice(1).filter(r => r.similarity > 0.4); if (additionalChunks.length > 0) { output.push(` + ${additionalChunks.length} more relevant section(s)`); } output.push(''); resultCount++; } if (resultsByNote.size === 0) { return 'No content found above the similarity threshold. Try using a lower threshold with -t option.'; } return output.join('\n'); } formatRelatedNotes(related, graph) { if (related.length === 0) { return 'No related notes found.'; } const output = []; output.push('RELATED NOTES:\n'); // Group by relationship type const byType = new Map(); for (const item of related) { if (!byType.has(item.type)) { byType.set(item.type, []); } byType.get(item.type).push({ path: item.path, reason: item.reason }); } // Display each type const typeOrder = ['direct', 'cluster', 'similar', 'tags']; const typeNames = { direct: 'DIRECTLY LINKED', cluster: 'SAME CLUSTER', similar: 'SIMILAR STRUCTURE', tags: 'SHARED TAGS' }; for (const relType of typeOrder) { if (byType.has(relType)) { const items = byType.get(relType); output.push(`${typeNames[relType]}:`); for (let i = 0; i < Math.min(5, items.length); i++) { const item = items[i]; const node = graph.nodes.get(item.path); const title = node ? node.note.title : path.basename(item.path); if (item.reason) { output.push(`- ${title} (${item.reason})`); } else { output.push(`- ${title}`); } } if (items.length > 5) { output.push(` ... and ${items.length - 5} more`); } output.push(''); } } return output.join('\n'); } } exports.LLMFormatter = LLMFormatter; //# sourceMappingURL=LLMFormatter.js.map