semem
Version:
Semantic Memory for Intelligent Agents
1,515 lines (1,386 loc) • 103 kB
JavaScript
#!/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,