UNPKG

semem

Version:

Semantic Memory for Intelligent Agents

1,515 lines (1,386 loc) 103 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { fileURLToPath } from 'url'; import path from 'path'; import dotenv from 'dotenv'; // Load environment variables from .env file dotenv.config(); // Import Semem APIs import MemoryManager from '../src/MemoryManager.js'; import Config from '../src/Config.js'; import { decomposeCorpus } from '../src/ragno/decomposeCorpus.js'; import Entity from '../src/ragno/Entity.js'; import SemanticUnit from '../src/ragno/SemanticUnit.js'; import Relationship from '../src/ragno/Relationship.js'; import CorpuscleSelector from '../src/zpt/selection/CorpuscleSelector.js'; import ContentChunker from '../src/zpt/transform/ContentChunker.js'; // Import LLM Connectors import OllamaConnector from '../src/connectors/OllamaConnector.js'; import ClaudeConnector from '../src/connectors/ClaudeConnector.js'; import MistralConnector from '../src/connectors/MistralConnector.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Create MCP server instance const server = new McpServer({ name: "Semem Integration Server", version: "1.0.0", instructions: "Provides access to Semem core, Ragno knowledge graph, and ZPT APIs for semantic memory management and knowledge processing" }); // Global instances for reuse let memoryManager = null; let config = null; // Safe operation wrappers for MCP tools - handle edge cases without modifying core components const safeOperations = { async retrieveMemories(query, threshold = 0.7, excludeLastN = 0) { // Input validation - handle empty/invalid queries gracefully if (!query || typeof query !== 'string' || !query.trim()) { return []; // Return empty array for invalid queries } return await memoryManager.retrieveRelevantInteractions(query.trim(), threshold, excludeLastN); }, async extractConcepts(text) { // Input validation - handle empty/invalid text gracefully if (!text || typeof text !== 'string' || !text.trim()) { return []; // Return empty array for invalid text } const concepts = await memoryManager.llmHandler.extractConcepts(text.trim()); // Additional parsing for MCP - handle LLM responses with prefixes like "[JSON]" if (Array.isArray(concepts)) { return concepts; } // If not already parsed, try to extract JSON array from string response if (typeof concepts === 'string') { return this.extractJsonArrayFromResponse(concepts); } return []; }, extractJsonArrayFromResponse(response) { // Handle responses with prefixes like "[JSON] ["concept1", "concept2"]" // Also handle nested arrays like [["term1"], ["term2"]] // Remove the [JSON] prefix if present let cleanResponse = response.replace(/^\[JSON\]\s*/, ''); // Look for the first complete JSON array structure let bracketDepth = 0; let startIndex = -1; let inString = false; let escapeNext = false; for (let i = 0; i < cleanResponse.length; i++) { const char = cleanResponse[i]; if (escapeNext) { escapeNext = false; continue; } if (char === '\\') { escapeNext = true; continue; } if (char === '"' && !escapeNext) { inString = !inString; continue; } if (inString) continue; if (char === '[') { if (bracketDepth === 0) { startIndex = i; } bracketDepth++; } else if (char === ']') { bracketDepth--; if (bracketDepth === 0 && startIndex !== -1) { const jsonCandidate = cleanResponse.substring(startIndex, i + 1); try { const parsed = JSON.parse(jsonCandidate); // If it's a nested array structure, flatten it if (Array.isArray(parsed) && parsed.length > 0 && Array.isArray(parsed[0])) { return parsed.flat(); } return parsed; } catch (e) { // Continue searching for a valid JSON structure startIndex = -1; continue; } } } } return []; } }; // Create LLM connector based on available configuration - following working examples pattern function createLLMConnector() { // Priority: Ollama (no API key needed) > Claude > Mistral if (process.env.OLLAMA_HOST || !process.env.CLAUDE_API_KEY) { console.log('Creating Ollama connector (preferred for local development)...'); return new OllamaConnector(); } else if (process.env.CLAUDE_API_KEY) { console.log('Creating Claude connector...'); return new ClaudeConnector(); } else if (process.env.MISTRAL_API_KEY) { console.log('Creating Mistral connector...'); return new MistralConnector(); } else { // Fallback to Ollama (most examples use this) console.log('Defaulting to Ollama connector...'); return new OllamaConnector(); } } // Initialize core services with proper error handling async function initializeServices() { try { if (!config) { console.error('Initializing config...'); config = new Config(path.join(process.cwd(), 'config', 'config.json')); await config.init(); console.error('Config initialized successfully'); } if (!memoryManager) { console.error('Initializing memory manager...'); // Check for available LLM providers from config const llmProviders = config.get('llmProviders') || []; const ollamaHost = process.env.OLLAMA_HOST; const claudeKey = process.env.CLAUDE_API_KEY; const mistralKey = process.env.MISTRAL_API_KEY; const openaiKey = process.env.OPENAI_API_KEY; const hasOllama = ollamaHost && ollamaHost !== ''; const hasClaude = claudeKey && claudeKey !== ''; const hasMistral = mistralKey && mistralKey !== ''; const hasOpenAI = openaiKey && openaiKey !== ''; const hasConfigProviders = llmProviders.length > 0; if (!hasOllama && !hasClaude && !hasMistral && !hasOpenAI && !hasConfigProviders) { console.warn('No LLM provider API keys found. Some features may be limited.'); console.warn('Consider setting API keys in .env file or configuring providers in config.json'); } else { const availableProviders = []; if (hasOllama) availableProviders.push('Ollama'); if (hasClaude) availableProviders.push('Claude'); if (hasMistral) availableProviders.push('Mistral'); if (hasOpenAI) availableProviders.push('OpenAI'); if (hasConfigProviders) availableProviders.push(`Config providers (${llmProviders.length})`); console.log(`Available LLM providers: ${availableProviders.join(', ')}`); } // Create LLM connector const llmProvider = createLLMConnector(); // Use working model names that exist in Ollama (following examples pattern) const chatModel = 'qwen2:1.5b'; // Known working model const embeddingModel = 'nomic-embed-text'; // Known working model // Initialize MemoryManager with proper parameters (following working examples) memoryManager = new MemoryManager({ llmProvider, chatModel, embeddingModel, storage: null // Will use default in-memory storage }); await memoryManager.initialize(); console.error('Memory manager initialized successfully'); } } catch (error) { console.error('Service initialization failed:', error.message); console.error('Some tools may have limited functionality'); // Create a minimal fallback config if needed if (!config) { config = { get: (key) => { const defaults = { 'chatModel': 'qwen2:1.5b', 'embeddingModel': 'nomic-embed-text', 'sparqlEndpoints': [] }; return defaults[key]; } }; } } } // === SEMEM CORE API TOOLS === // Memory Management Tools server.tool( "semem_store_interaction", { prompt: z.string().describe("The user prompt/input"), response: z.string().describe("The AI response/output"), metadata: z.object({}).optional().describe("Additional metadata for the interaction") }, { description: "Store a conversation interaction in semantic memory with concept extraction and embeddings" }, async ({ prompt, response, metadata = {} }) => { try { await initializeServices(); if (memoryManager) { // Generate embedding and extract concepts const embedding = await memoryManager.generateEmbedding(prompt); const concepts = await memoryManager.extractConcepts(response); // Store the interaction await memoryManager.addInteraction(prompt, response, embedding, concepts, metadata); return { content: [{ type: "text", text: JSON.stringify({ success: true, message: `Successfully stored interaction with ${concepts.length} concepts extracted`, prompt: prompt.substring(0, 50) + "...", conceptCount: concepts.length, metadata }, null, 2) }] }; } else { // Fallback demo response const mockConcepts = ["artificial intelligence", "machine learning", "technology"]; return { content: [{ type: "text", text: JSON.stringify({ success: true, message: `Demo: Stored interaction with ${mockConcepts.length} concepts extracted`, prompt: prompt.substring(0, 50) + "...", conceptCount: mockConcepts.length, concepts: mockConcepts, metadata, note: "Demo mode - memory manager not available" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error storing interaction: ${error.message}`, prompt: prompt.substring(0, 50) + "..." }, null, 2) }], isError: true }; } } ); server.tool( "semem_retrieve_memories", { query: z.string().describe("Search query to find relevant memories"), threshold: z.number().optional().default(0.7).describe("Similarity threshold (0-1)"), limit: z.number().optional().default(10).describe("Maximum number of results"), excludeLastN: z.number().optional().default(0).describe("Exclude the last N interactions") }, { description: "Retrieve semantically similar memories using vector similarity search" }, async ({ query, threshold, limit, excludeLastN }) => { try { await initializeServices(); if (memoryManager) { const retrievals = await safeOperations.retrieveMemories( query, threshold, excludeLastN ); // Limit results const limitedResults = retrievals.slice(0, limit); return { content: [{ type: "text", text: JSON.stringify({ success: true, query, threshold, count: limitedResults.length, memories: limitedResults.map(r => ({ prompt: r.prompt, response: r.response, similarity: r.similarity, concepts: r.concepts, timestamp: r.timestamp })) }, null, 2) }] }; } else { // Demo fallback - create mock memories const mockMemories = [ { prompt: "What is artificial intelligence?", response: "AI is a broad field of computer science focused on creating systems that can perform tasks that typically require human intelligence.", similarity: 0.89, concepts: ["artificial intelligence", "computer science", "human intelligence"], timestamp: new Date().toISOString() }, { prompt: "Explain machine learning", response: "Machine learning is a subset of AI that enables computers to learn and improve from experience without being explicitly programmed.", similarity: 0.76, concepts: ["machine learning", "artificial intelligence", "algorithms"], timestamp: new Date().toISOString() } ].filter(memory => memory.prompt.toLowerCase().includes(query.toLowerCase()) || memory.response.toLowerCase().includes(query.toLowerCase()) ).slice(0, limit); return { content: [{ type: "text", text: JSON.stringify({ success: true, query, threshold, count: mockMemories.length, memories: mockMemories, note: "Demo mode - using mock memories" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error retrieving memories: ${error.message}`, query }, null, 2) }], isError: true }; } } ); server.tool( "semem_generate_response", { description: "Generate an AI response using memory context and LLM integration", parameters: {prompt: z.string().describe("The input prompt"), useMemory: z.boolean().optional().default(true).describe("Whether to use memory for context"), contextWindow: z.number().optional().default(4000).describe("Context window size in tokens"), temperature: z.number().optional().default(0.7).describe("Response temperature (0-1)") } }, async ({ prompt, useMemory, contextWindow, temperature }) => { try { await initializeServices(); if (memoryManager) { let retrievals = []; let lastInteractions = []; if (useMemory) { // Get relevant memories and recent interactions retrievals = await memoryManager.retrieveRelevantInteractions(prompt, 0.7, 0); // Get last few interactions for context lastInteractions = await memoryManager.retrieveRelevantInteractions("all", 0, 0); lastInteractions = lastInteractions.slice(-3); // Last 3 interactions } const response = await memoryManager.generateResponse( prompt, lastInteractions, retrievals, contextWindow, { temperature } ); return { content: [{ type: "text", text: JSON.stringify({ success: true, prompt, response, memoryUsed: useMemory, retrievalCount: retrievals.length, contextCount: lastInteractions.length, temperature }, null, 2) }] }; } else { // Demo fallback - generate mock response const demoResponses = { "machine learning": "Machine learning is a powerful subset of AI that enables systems to automatically learn and improve from experience without being explicitly programmed for every scenario.", "neural networks": "Neural networks are computational models inspired by biological neural networks, consisting of interconnected nodes that process information through weighted connections.", "default": "This is a demo response generated by the Semem MCP server. In a full deployment, this would be generated using configured LLM providers with semantic memory context." }; const responseKey = Object.keys(demoResponses).find(key => prompt.toLowerCase().includes(key) ) || "default"; return { content: [{ type: "text", text: JSON.stringify({ success: true, prompt, response: demoResponses[responseKey], memoryUsed: useMemory, retrievalCount: useMemory ? 2 : 0, contextCount: useMemory ? 1 : 0, temperature, note: "Demo mode - using mock response generation" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error generating response: ${error.message}`, prompt }, null, 2) }], isError: true }; } } ); // Embedding and Concept Tools server.tool( "semem_generate_embedding", { description: "Generate vector embeddings for text using the configured embedding model", parameters: {text: z.string().describe("Text to generate embedding for"), model: z.string().optional().describe("Embedding model to use") } }, async ({ text, model }) => { try { await initializeServices(); if (memoryManager) { const embedding = await memoryManager.generateEmbedding(text, model); return { content: [{ type: "text", text: JSON.stringify({ success: true, text: text.substring(0, 100) + (text.length > 100 ? '...' : ''), embeddingLength: embedding.length, model: model || 'default', embedding: embedding.slice(0, 10).concat(['...']) // Show first 10 dims }, null, 2) }] }; } else { // Demo fallback - generate mock embedding const mockEmbedding = Array.from({ length: 1536 }, () => Math.random() * 2 - 1); return { content: [{ type: "text", text: JSON.stringify({ success: true, text: text.substring(0, 100) + (text.length > 100 ? '...' : ''), embeddingLength: mockEmbedding.length, model: model || 'mock-embedding-model', embedding: mockEmbedding.slice(0, 10).concat(['...']), note: "Demo mode - using mock embedding" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error generating embedding: ${error.message}`, text: text.substring(0, 50) + "..." }, null, 2) }], isError: true }; } } ); server.tool( "semem_extract_concepts", { description: "Extract semantic concepts from text using LLM analysis", parameters: {text: z.string().describe("Text to extract concepts from") } }, async ({ text }) => { try { await initializeServices(); if (memoryManager) { const concepts = await safeOperations.extractConcepts(text); return { content: [{ type: "text", text: JSON.stringify({ success: true, text: text.substring(0, 100) + (text.length > 100 ? '...' : ''), conceptCount: concepts.length, concepts }, null, 2) }] }; } else { // Demo fallback - simple keyword extraction const words = text.toLowerCase().match(/\b\w+\b/g) || []; const concepts = [...new Set(words)] .filter(word => word.length > 4) .slice(0, 8) .sort(); return { content: [{ type: "text", text: JSON.stringify({ success: true, text: text.substring(0, 100) + (text.length > 100 ? '...' : ''), conceptCount: concepts.length, concepts, note: "Demo mode - using simple keyword extraction" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error extracting concepts: ${error.message}`, text: text.substring(0, 50) + "..." }, null, 2) }], isError: true }; } } ); // === DOCUMENT MANAGEMENT TOOLS === server.tool( "store_document", { content: z.string().describe("Document content to store"), metadata: z.object({ title: z.string().optional(), source: z.string().optional(), author: z.string().optional(), created: z.string().optional(), type: z.string().optional(), tags: z.array(z.string()).optional() }).optional().describe("Document metadata") }, { description: "Store a document with metadata, generate embeddings, and extract concepts for GraphRAG" }, async ({ content, metadata = {} }) => { try { await initializeServices(); if (memoryManager) { // Generate embedding for the document const embedding = await memoryManager.generateEmbedding(content); // Extract concepts from the document const concepts = await memoryManager.extractConcepts(content); // Create document ID const documentId = `doc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Store as memory interaction with document metadata const documentMetadata = { ...metadata, documentId, type: 'document', contentLength: content.length, timestamp: new Date().toISOString() }; await memoryManager.addInteraction( `Document: ${metadata.title || 'Untitled'}`, content, embedding, concepts, documentMetadata ); return { content: [{ type: "text", text: JSON.stringify({ success: true, documentId, title: metadata.title || 'Untitled', contentLength: content.length, conceptCount: concepts.length, metadata: documentMetadata }, null, 2) }] }; } else { // Demo fallback const documentId = `demo_doc_${Date.now()}`; const mockConcepts = content.split(/\s+/).filter(word => word.length > 4).slice(0, 5); return { content: [{ type: "text", text: JSON.stringify({ success: true, documentId, title: metadata.title || 'Untitled', contentLength: content.length, conceptCount: mockConcepts.length, concepts: mockConcepts, metadata: { ...metadata, documentId, type: 'document' }, note: "Demo mode - document stored in memory" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error storing document: ${error.message}` }, null, 2) }], isError: true }; } } ); server.tool( "list_documents", { limit: z.number().optional().default(50).describe("Maximum number of documents to return"), offset: z.number().optional().default(0).describe("Number of documents to skip"), filter: z.object({ type: z.string().optional(), author: z.string().optional(), tags: z.array(z.string()).optional(), dateRange: z.object({ start: z.string().optional(), end: z.string().optional() }).optional() }).optional().describe("Filters to apply") }, async ({ limit, offset, filter = {} }) => { try { await initializeServices(); if (memoryManager) { // Retrieve all memories and filter for documents const allMemories = await memoryManager.retrieveRelevantInteractions("all", 0, 0); let documents = allMemories .filter(memory => memory.metadata?.type === 'document') .map(memory => ({ documentId: memory.metadata.documentId, title: memory.metadata.title || 'Untitled', author: memory.metadata.author, source: memory.metadata.source, created: memory.metadata.created || memory.timestamp, type: memory.metadata.documentType || 'text', tags: memory.metadata.tags || [], contentLength: memory.metadata.contentLength || memory.response.length, conceptCount: memory.concepts?.length || 0, timestamp: memory.timestamp })); // Apply filters if (filter.type) { documents = documents.filter(doc => doc.type === filter.type); } if (filter.author) { documents = documents.filter(doc => doc.author?.includes(filter.author)); } if (filter.tags?.length > 0) { documents = documents.filter(doc => filter.tags.some(tag => doc.tags.includes(tag)) ); } if (filter.dateRange) { const start = filter.dateRange.start ? new Date(filter.dateRange.start) : new Date(0); const end = filter.dateRange.end ? new Date(filter.dateRange.end) : new Date(); documents = documents.filter(doc => { const docDate = new Date(doc.created); return docDate >= start && docDate <= end; }); } // Apply pagination const paginatedDocs = documents.slice(offset, offset + limit); return { content: [{ type: "text", text: JSON.stringify({ success: true, total: documents.length, returned: paginatedDocs.length, offset, limit, documents: paginatedDocs }, null, 2) }] }; } else { // Demo fallback const mockDocuments = [ { documentId: "demo_doc_1", title: "Sample Document 1", author: "Demo Author", type: "text", tags: ["demo", "sample"], contentLength: 1500, conceptCount: 8, created: new Date().toISOString() }, { documentId: "demo_doc_2", title: "Sample Document 2", type: "research", tags: ["research", "ai"], contentLength: 2300, conceptCount: 12, created: new Date().toISOString() } ].slice(offset, offset + limit); return { content: [{ type: "text", text: JSON.stringify({ success: true, total: 2, returned: mockDocuments.length, offset, limit, documents: mockDocuments, note: "Demo mode - showing mock documents" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error listing documents: ${error.message}` }, null, 2) }], isError: true }; } } ); server.tool( "delete_documents", { description: "Delete one or more documents by their IDs", parameters: { documentIds: z.array(z.string()).describe("Array of document IDs to delete"), confirmDelete: z.boolean().default(false).describe("Confirm deletion (safety check)") } }, async ({ documentIds, confirmDelete }) => { try { if (!confirmDelete) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Deletion not confirmed. Set confirmDelete to true to proceed.", documentsToDelete: documentIds.length }, null, 2) }] }; } await initializeServices(); if (memoryManager) { // Note: Current MemoryManager doesn't have direct delete functionality // In a full implementation, this would require extending MemoryManager // For now, we'll provide feedback about what would be deleted const allMemories = await memoryManager.retrieveRelevantInteractions("all", 0, 0); const documentsToDelete = allMemories.filter(memory => memory.metadata?.type === 'document' && documentIds.includes(memory.metadata.documentId) ); return { content: [{ type: "text", text: JSON.stringify({ success: true, note: "Delete functionality requires MemoryManager enhancement", requestedDeletions: documentIds.length, foundDocuments: documentsToDelete.length, foundDocumentIds: documentsToDelete.map(doc => doc.metadata.documentId), message: "In full implementation, these documents would be deleted from storage" }, null, 2) }] }; } else { // Demo mode return { content: [{ type: "text", text: JSON.stringify({ success: true, deletedDocuments: documentIds.length, deletedIds: documentIds, note: "Demo mode - documents would be deleted in real implementation" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error deleting documents: ${error.message}` }, null, 2) }], isError: true }; } } ); // === RELATIONSHIP MANAGEMENT TOOLS === server.tool( "create_relations", { sourceEntity: z.string().describe("Source entity URI or ID"), targetEntity: z.string().describe("Target entity URI or ID"), relationshipType: z.string().describe("Type of relationship (e.g., 'relatedTo', 'partOf', 'causes')"), description: z.string().optional().describe("Human-readable description of the relationship"), weight: z.number().optional().default(1.0).describe("Relationship strength/weight (0-1)"), metadata: z.object({}).optional().describe("Additional relationship metadata") }, async ({ sourceEntity, targetEntity, relationshipType, description, weight, metadata = {} }) => { try { await initializeServices(); if (memoryManager && memoryManager.llmHandler) { // Create relationship using Ragno's Relationship class const relationship = new Relationship({ source: sourceEntity, target: targetEntity, type: relationshipType, description: description || `${sourceEntity} ${relationshipType} ${targetEntity}`, weight: weight, metadata: { ...metadata, created: new Date().toISOString(), createdBy: 'mcp-server' } }); // In a full implementation, this would be stored in SPARQL store // For now, we'll store it as a memory interaction const relationshipData = { type: 'relationship', relationship: { source: sourceEntity, target: targetEntity, relationshipType, description, weight, id: relationship.uri || `rel_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, metadata } }; await memoryManager.addInteraction( `Relationship: ${sourceEntity} ${relationshipType} ${targetEntity}`, JSON.stringify(relationshipData), null, // No embedding for relationships [sourceEntity, targetEntity, relationshipType], relationshipData ); return { content: [{ type: "text", text: JSON.stringify({ success: true, relationship: relationshipData.relationship, message: "Relationship created successfully" }, null, 2) }] }; } else { // Demo fallback const relationshipId = `demo_rel_${Date.now()}`; return { content: [{ type: "text", text: JSON.stringify({ success: true, relationship: { id: relationshipId, source: sourceEntity, target: targetEntity, relationshipType, description: description || `${sourceEntity} ${relationshipType} ${targetEntity}`, weight, metadata: { ...metadata, created: new Date().toISOString() } }, note: "Demo mode - relationship would be stored in graph database" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error creating relationship: ${error.message}` }, null, 2) }], isError: true }; } } ); server.tool( "search_relations", { description: "Search for relationships by entity, type, or other criteria", parameters: {entityId: z.string().optional().describe("Entity ID to search relations for"), relationshipType: z.string().optional().describe("Filter by relationship type"), direction: z.enum(['outgoing', 'incoming', 'both']).optional().default('both').describe("Relationship direction"), limit: z.number().optional().default(50).describe("Maximum relationships to return"), minWeight: z.number().optional().default(0).describe("Minimum relationship weight") } }, async ({ entityId, relationshipType, direction, limit, minWeight }) => { try { await initializeServices(); if (memoryManager) { // Retrieve all memories and filter for relationships const allMemories = await memoryManager.retrieveRelevantInteractions("all", 0, 0); let relationships = allMemories .filter(memory => memory.metadata?.type === 'relationship') .map(memory => memory.metadata.relationship) .filter(rel => rel != null); // Apply filters if (entityId) { relationships = relationships.filter(rel => { switch (direction) { case 'outgoing': return rel.source === entityId; case 'incoming': return rel.target === entityId; case 'both': default: return rel.source === entityId || rel.target === entityId; } }); } if (relationshipType) { relationships = relationships.filter(rel => rel.relationshipType === relationshipType); } if (minWeight > 0) { relationships = relationships.filter(rel => (rel.weight || 0) >= minWeight); } // Limit results relationships = relationships.slice(0, limit); return { content: [{ type: "text", text: JSON.stringify({ success: true, query: { entityId, relationshipType, direction, minWeight }, count: relationships.length, relationships }, null, 2) }] }; } else { // Demo fallback const mockRelationships = [ { id: "demo_rel_1", source: "entity_ai", target: "entity_ml", relationshipType: "includes", description: "AI includes machine learning", weight: 0.9, metadata: { created: new Date().toISOString() } }, { id: "demo_rel_2", source: "entity_ml", target: "entity_dl", relationshipType: "includes", description: "Machine learning includes deep learning", weight: 0.8, metadata: { created: new Date().toISOString() } } ].filter(rel => { if (entityId) { switch (direction) { case 'outgoing': return rel.source === entityId; case 'incoming': return rel.target === entityId; case 'both': default: return rel.source === entityId || rel.target === entityId; } } return true; }).slice(0, limit); return { content: [{ type: "text", text: JSON.stringify({ success: true, query: { entityId, relationshipType, direction, minWeight }, count: mockRelationships.length, relationships: mockRelationships, note: "Demo mode - showing mock relationships" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error searching relationships: ${error.message}` }, null, 2) }], isError: true }; } } ); server.tool( "delete_relations", { description: "Delete relationships from the knowledge graph by ID", parameters: {relationshipIds: z.array(z.string()).describe("Array of relationship IDs to delete"), confirmDelete: z.boolean().default(false).describe("Confirm deletion (safety check)") } }, async ({ relationshipIds, confirmDelete }) => { try { if (!confirmDelete) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Deletion not confirmed. Set confirmDelete to true to proceed.", relationshipsToDelete: relationshipIds.length }, null, 2) }] }; } await initializeServices(); if (memoryManager) { // Note: Similar to document deletion, this requires MemoryManager enhancement const allMemories = await memoryManager.retrieveRelevantInteractions("all", 0, 0); const relationshipsToDelete = allMemories.filter(memory => memory.metadata?.type === 'relationship' && relationshipIds.includes(memory.metadata.relationship?.id) ); return { content: [{ type: "text", text: JSON.stringify({ success: true, note: "Delete functionality requires MemoryManager enhancement", requestedDeletions: relationshipIds.length, foundRelationships: relationshipsToDelete.length, foundRelationshipIds: relationshipsToDelete.map(rel => rel.metadata.relationship.id), message: "In full implementation, these relationships would be deleted from graph" }, null, 2) }] }; } else { // Demo mode return { content: [{ type: "text", text: JSON.stringify({ success: true, deletedRelationships: relationshipIds.length, deletedIds: relationshipIds, note: "Demo mode - relationships would be deleted in real implementation" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error deleting relationships: ${error.message}` }, null, 2) }], isError: true }; } } ); // === RAGNO API TOOLS === server.tool( "ragno_decompose_corpus", { textChunks: z.array(z.string()).describe("Array of text chunks to decompose"), options: z.object({ maxEntities: z.number().optional().default(100), minFrequency: z.number().optional().default(1), extractRelationships: z.boolean().optional().default(true) }).optional().describe("Decomposition options") }, async ({ textChunks, options = {} }) => { try { await initializeServices(); if (memoryManager && memoryManager.llmHandler) { // Get LLM handler from memory manager const llmHandler = memoryManager.llmHandler; const result = await decomposeCorpus(textChunks, llmHandler, options); return { content: [{ type: "text", text: JSON.stringify({ success: true, statistics: result.statistics, unitCount: result.units.length, entityCount: result.entities.length, relationshipCount: result.relationships.length, entities: result.entities.slice(0, 5).map(e => ({ name: e.getName(), frequency: e.frequency, isEntryPoint: e.isEntryPoint() })), relationships: result.relationships.slice(0, 5).map(r => ({ source: r.source, target: r.target, description: r.description, weight: r.weight })) }, null, 2) }] }; } else { // Demo fallback - create mock decomposition result const mockEntities = textChunks.flatMap(chunk => chunk.match(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b/g) || [] ) .filter((entity, index, arr) => arr.indexOf(entity) === index) .slice(0, options.maxEntities || 10) .map((name, i) => ({ name, frequency: Math.floor(Math.random() * 5) + 1, isEntryPoint: i < 3 })); const mockRelationships = []; for (let i = 0; i < Math.min(mockEntities.length - 1, 5); i++) { mockRelationships.push({ source: mockEntities[i].name, target: mockEntities[i + 1].name, description: "related_to", weight: Math.random().toFixed(2) }); } return { content: [{ type: "text", text: JSON.stringify({ success: true, statistics: { totalChunks: textChunks.length, totalTokens: textChunks.join(' ').split(/\s+/).length, processingTime: "demo" }, unitCount: textChunks.length, entityCount: mockEntities.length, relationshipCount: mockRelationships.length, entities: mockEntities.slice(0, 5), relationships: mockRelationships, note: "Demo mode - using mock corpus decomposition" }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Error decomposing corpus: ${error.message}`, textChunkCount: textChunks.length }, null, 2) }], isError: true }; } } ); server.tool( "ragno_create_entity", { description: "Create an RDF entity with semantic properties and metadata", parameters: {name: z.string().describe("Entity name"), isEntryPoint: z.boolean().optional().default(false).describe("Whether this is an entry point entity"), subType: z.string().optional().describe("Entity subtype"), frequency: z.number().optional().default(1).describe("Entity frequency/importance") } }, async ({ name, isEntryPoint, subType, frequency }) => { try { const entity = new Entity({ name, isEntryPoint, subType, frequency }); return { content: [{ type: "text", text: JSON.stringify({ created: true, entity: { name: entity.getName(), prefLabel: entity.getPrefLabel(), isEntryPoint: entity.isEntryPoint(), subType: entity.getSubType(), frequency: entity.frequency } }, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error creating entity: ${error.message}` }], isError: true }; } } ); server.tool( "ragno_create_semantic_unit", { description: "Create a semantic text unit from corpus decomposition", parameters: {text: z.string().describe("Text content of the semantic unit"), summary: z.string().optional().describe("Summary of the unit"), source: z.string().optional().describe("Source identifier"), position: z.number().optional().describe("Position in source"), length: z.number().optional().describe("Length of the unit") } }, async ({ text, summary, source, position, length }) => { try { const unit = new SemanticUnit({ text, summary, source, position, length }); return { content: [{ type: "text", text: JSON.stringify({ created: true, unit: { text: unit.getText().substring(0, 100) + (unit.getText().length > 100 ? '...' : ''), summary: unit.getSummary(), source: unit.source, position: unit.position, length: unit.length } }, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error creating semantic unit: ${error.message}` }], isError: true }; } } ); // === ZPT API TOOLS === server.tool( "zpt_select_corpuscles", { zoom: z.enum(['entity', 'unit', 'text', 'community', 'corpus']).describe("ZPT zoom level (required)"), pan: z.object({ topic: z.string().optional(), entity: z.array(z.string()).optional(), temporal: z.object({ start: z.string().optional(), end: z.string().optional() }).optional() }).optional().describe("ZPT pan parameters"), tilt: z.enum(['embedding', 'keywords', 'graph', 'temporal']).describe("ZPT tilt perspective (required)"), selectionType: z.enum(['embedding', 'keywords', 'graph', 'temporal']).describe("Type of selection"), criteria: z.any().describe("Selection criteria specific to the type"), limit: z.number().optional().default(10).describe("Maximum results") }, async ({ zoom, pan = {}, tilt, selectionType, criteria, limit }) => { try { await initializeServices(); // Check if we have the required services for ZPT operations if (memoryManager && memoryManager.embeddingHandler) { // Construct proper ZPT parameters const zptParams = { zoom, pan, tilt, selectionType, criteria, limit }; const selector = new CorpuscleSelector(); const results = await selector.select(zptParams); return { content: [{ type: "text", text: JSON.stringify({ success: true, zoom, tilt, selectionType, resultCount: results.length, results: results.slice(0, 10) // Limit output }, null, 2) }] }; } else { // Use demo mode when services not available throw new Error("Services not available - using demo mode"); } } catch (error) { // Fallback demo response const mockResults = [ { id: "demo_1", relevance: 0.95, type: selectionType, title: `Sample ${selectionType} result 1`, content: `This is a demo result for ${selectionType} selection at ${zoom} zoom level.` }, { id: "demo_2", relevance: 0.87, type: selectionType, title: `Sample ${selectionType} result 2`, content: `Another demo result showcasing ${tilt} perspective.` } ]; return { content: [{ type: "text", text: JSON.stringify({ success: true, zoom, tilt, selectionType, resultCount: mockResults.length, results: mockResults, note: `Demo mode - services not available for ZPT operations` }, null, 2) }] }; } } ); server.tool( "zpt_chunk_content", { content: z.string().describe("Content to chunk"), options: z.object({ method: z.enum(['fixed', 'semantic', 'adaptive', 'token-aware', 'hierarchical']).describe("Chunking method"), chunkSize: z.number().optional().default(1000).describe("Target chunk size"), overlap: z.number().optional().default(100).describe("Overlap between chunks"), preserveStructure: z.boolean().optional().default(true).describe("Preserve document structure") }).describe("Chunking options") }, async ({ content, options }) => { try { const chunker = new ContentChunker(); const chunks = await chunker.chunk(content, options); return { content: [{ type: "text", text: JSON.stringify({ success: true, method: options.method, originalLength: content.length,