Elasticsearch Knowledge Graph for MCP

by j3k0
  • legacy
#!/usr/bin/env node import { promises as fs } from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { ScoredKnowledgeGraph, searchGraph } from './query-language.js'; import { KnowledgeGraph, Relation } from './types.js'; // Define memory file path using environment variable with fallback const defaultMemoryPath = path.join(process.cwd(), 'memory.json'); // If MEMORY_FILE_PATH is just a filename, put it in the current working directory const MEMORY_FILE_PATH = process.env.MEMORY_FILE_PATH ? path.isAbsolute(process.env.MEMORY_FILE_PATH) ? process.env.MEMORY_FILE_PATH : path.join(process.cwd(), process.env.MEMORY_FILE_PATH) : defaultMemoryPath; /** * Loads the knowledge graph from the memory file */ async function loadGraph(): Promise<KnowledgeGraph> { try { const data = await fs.readFile(MEMORY_FILE_PATH, "utf-8"); const lines = data.split("\n").filter(line => line.trim() !== ""); const graph: KnowledgeGraph = { entities: [], relations: [] }; for (let i = 0; i < lines.length; i++) { const line = lines[i]; try { const item = JSON.parse(line); if (item.type === "entity") { const entity = { name: item.name, entityType: item.entityType, observations: item.observations || [] }; graph.entities.push(entity); } else if (item.type === "relation") { const relation = { from: item.from, to: item.to, relationType: item.relationType }; graph.relations.push(relation); } } catch (e) { console.error(`Error parsing line: ${(e as Error).message}`); } } return graph; } catch (error) { if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") { return { entities: [], relations: [] }; } throw error; } } /** * Formats and prints the search results */ function printResults(graph: ScoredKnowledgeGraph, relations: Relation[]): void { // Format entities console.log("\n=== ENTITIES ==="); if (graph.scoredEntities.length === 0) { console.log("No entities found matching the query."); } else { graph.scoredEntities.forEach((scoredEntity, index) => { const entity = scoredEntity.entity; console.log(`\n[${index + 1}: @${Math.round(scoredEntity.score * 10) * 0.1}] ${entity.name} (${entity.entityType})`); if (entity.observations.length > 0) { console.log(" Observations:"); entity.observations.forEach(obs => { console.log(` - ${obs}`); }); } }); } // Format relations console.log("\n=== RELATIONS ==="); if (relations.length === 0) { console.log("No relations found between the matched entities."); } else { relations.forEach((relation, index) => { console.log(`[${index + 1}] ${relation.from} ${relation.relationType} ${relation.to}`); }); } console.log(""); } /** * Prints usage information */ function printUsage(): void { console.log(` Usage: memory-query [OPTIONS] QUERY Search the knowledge graph using the query language. Query Language: - type:value Filter entities by type - name:value Filter entities by name - +word Require this term (AND logic) - -word Exclude this term (NOT logic) - word1|word2|word3 Match any of these terms (OR logic) - Any other text Used for fuzzy matching Examples: memory-query type:person +programmer -manager memory-query "frontend|backend developer" memory-query name:john memory-query "type:project +active -completed priority|urgent" Options: -h, --help Show this help message -j, --json Output results in JSON format `); } /** * Main function */ async function main(): Promise<void> { const args = process.argv.slice(2); // Check for help flag if (args.includes('-h') || args.includes('--help') || args.length === 0) { printUsage(); return; } // Check for JSON output flag const jsonOutput = args.includes('-j') || args.includes('--json'); // Remove flags from args const cleanArgs = args.filter(arg => !arg.startsWith('-')); // Combine all non-flag arguments as the query const query = cleanArgs.join(' '); try { const graph = await loadGraph(); const results = searchGraph(query, graph); const names: { [name: string]: boolean } = results.scoredEntities.reduce((acc: { [name: string]: boolean }, se) => { acc[se.entity.name] = true; return acc; }, {}); const relations = graph.relations.filter(r => names[r.from] && names[r.to]); if (jsonOutput) { // Output as JSON console.log(JSON.stringify(results, null, 2)); } else { // Output in human-readable format printResults(results, relations); } } catch (error) { console.error("Error while searching the knowledge graph:", error); process.exit(1); } } main().catch(error => { console.error("Fatal error:", error); process.exit(1); });