Skip to main content
Glama

meMCP - Memory-Enhanced Model Context Protocol

MIT License
23
2
RelationshipManager.js12.8 kB
export class RelationshipManager { constructor(factStore) { this.factStore = factStore; this.relationshipTypes = { prevents: { name: 'prevents', description: 'This fact prevents or mitigates the target fact', inverse: 'prevented_by', weight: 1.0, }, enables: { name: 'enables', description: 'This fact enables or supports the target fact', inverse: 'enabled_by', weight: 0.8, }, requires: { name: 'requires', description: 'This fact requires the target fact as a prerequisite', inverse: 'required_by', weight: 0.9, }, conflicts: { name: 'conflicts', description: 'This fact conflicts or contradicts the target fact', inverse: 'conflicts', weight: 0.7, }, extends: { name: 'extends', description: 'This fact extends or builds upon the target fact', inverse: 'extended_by', weight: 0.6, }, similar: { name: 'similar', description: 'This fact is similar to the target fact', inverse: 'similar', weight: 0.5, }, }; } async createRelationship(sourceFactId, targetFactId, relationshipType, metadata = {}) { if (!this.isValidRelationshipType(relationshipType)) { throw new Error(`Invalid relationship type: ${relationshipType}`); } const sourceFact = this.factStore.factsIndex.get(sourceFactId); const targetFact = this.factStore.factsIndex.get(targetFactId); if (!sourceFact) { throw new Error(`Source fact ${sourceFactId} not found`); } if (!targetFact) { throw new Error(`Target fact ${targetFactId} not found`); } if (sourceFactId === targetFactId) { throw new Error('Cannot create relationship to self'); } const relationship = { id: `rel_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, sourceId: sourceFactId, targetId: targetFactId, type: relationshipType, weight: this.relationshipTypes[relationshipType].weight, createdAt: new Date().toISOString(), metadata: { confidence: metadata.confidence || 0.8, source: metadata.source || 'manual', ...metadata, }, }; await this.addRelationshipToFact(sourceFactId, relationship); const inverse = this.relationshipTypes[relationshipType].inverse; if (inverse && inverse !== relationshipType) { const inverseRelationship = { ...relationship, id: `rel_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, sourceId: targetFactId, targetId: sourceFactId, type: inverse, }; await this.addRelationshipToFact(targetFactId, inverseRelationship); } return relationship; } async addRelationshipToFact(factId, relationship) { const fact = this.factStore.factsIndex.get(factId); if (!fact) { throw new Error(`Fact ${factId} not found`); } if (!fact.relationships) { fact.relationships = []; } const existingIndex = fact.relationships.findIndex( rel => rel.targetId === relationship.targetId && rel.type === relationship.type ); if (existingIndex >= 0) { fact.relationships[existingIndex] = relationship; } else { fact.relationships.push(relationship); } await this.factStore.updateFact(factId, fact); } async removeRelationship(sourceFactId, targetFactId, relationshipType) { const sourceFact = this.factStore.factsIndex.get(sourceFactId); if (!sourceFact || !sourceFact.relationships) { return false; } const relationshipIndex = sourceFact.relationships.findIndex( rel => rel.targetId === targetFactId && rel.type === relationshipType ); if (relationshipIndex === -1) { return false; } sourceFact.relationships.splice(relationshipIndex, 1); await this.factStore.updateFact(sourceFactId, sourceFact); const inverse = this.relationshipTypes[relationshipType]?.inverse; if (inverse && inverse !== relationshipType) { const targetFact = this.factStore.factsIndex.get(targetFactId); if (targetFact && targetFact.relationships) { const inverseIndex = targetFact.relationships.findIndex( rel => rel.targetId === sourceFactId && rel.type === inverse ); if (inverseIndex >= 0) { targetFact.relationships.splice(inverseIndex, 1); await this.factStore.updateFact(targetFactId, targetFact); } } } return true; } async autoDiscoverRelationships(factId, maxSuggestions = 5) { const fact = this.factStore.factsIndex.get(factId); if (!fact) { throw new Error(`Fact ${factId} not found`); } const suggestions = []; const similarFacts = await this.findSimilarFacts(fact); const conflictingFacts = await this.findConflictingFacts(fact); const dependencyFacts = await this.findDependencyFacts(fact); for (const similarFact of similarFacts.slice(0, 2)) { suggestions.push({ type: 'similar', targetId: similarFact.id, confidence: similarFact.similarity, reason: 'Content similarity detected', }); } for (const conflictingFact of conflictingFacts.slice(0, 2)) { suggestions.push({ type: 'conflicts', targetId: conflictingFact.id, confidence: conflictingFact.conflictScore, reason: 'Potential conflict detected', }); } for (const dependencyFact of dependencyFacts.slice(0, 2)) { suggestions.push({ type: dependencyFact.relationType, targetId: dependencyFact.id, confidence: dependencyFact.dependencyScore, reason: dependencyFact.reason, }); } return suggestions .sort((a, b) => b.confidence - a.confidence) .slice(0, maxSuggestions); } async findSimilarFacts(fact) { const allFacts = Array.from(this.factStore.factsIndex.values()) .filter(f => f.id !== fact.id); const similarities = []; for (const otherFact of allFacts) { const similarity = this.calculateContentSimilarity(fact, otherFact); if (similarity > 0.6) { similarities.push({ ...otherFact, similarity, }); } } return similarities.sort((a, b) => b.similarity - a.similarity); } async findConflictingFacts(fact) { const allFacts = Array.from(this.factStore.factsIndex.values()) .filter(f => f.id !== fact.id); const conflicts = []; for (const otherFact of allFacts) { const conflictScore = this.detectConflict(fact, otherFact); if (conflictScore > 0.5) { conflicts.push({ ...otherFact, conflictScore, }); } } return conflicts.sort((a, b) => b.conflictScore - a.conflictScore); } async findDependencyFacts(fact) { const allFacts = Array.from(this.factStore.factsIndex.values()) .filter(f => f.id !== fact.id); const dependencies = []; for (const otherFact of allFacts) { const dependency = this.detectDependency(fact, otherFact); if (dependency) { dependencies.push({ ...otherFact, ...dependency, }); } } return dependencies.sort((a, b) => b.dependencyScore - a.dependencyScore); } calculateContentSimilarity(fact1, fact2) { const content1 = fact1.content.toLowerCase(); const content2 = fact2.content.toLowerCase(); let score = 0; if (fact1.type === fact2.type) { score += 0.2; } if (fact1.domain === fact2.domain) { score += 0.2; } const commonTags = fact1.tags.filter(tag => fact2.tags.includes(tag)); score += (commonTags.length / Math.max(fact1.tags.length, fact2.tags.length)) * 0.3; const words1 = new Set(content1.split(/\s+/)); const words2 = new Set(content2.split(/\s+/)); const intersection = new Set([...words1].filter(x => words2.has(x))); const union = new Set([...words1, ...words2]); if (union.size > 0) { score += (intersection.size / union.size) * 0.3; } return Math.min(1, score); } detectConflict(fact1, fact2) { const content1 = fact1.content.toLowerCase(); const content2 = fact2.content.toLowerCase(); let conflictScore = 0; if (fact1.type === 'verified_pattern' && fact2.type === 'anti_pattern') { const words1 = new Set(content1.split(/\s+/)); const words2 = new Set(content2.split(/\s+/)); const intersection = new Set([...words1].filter(x => words2.has(x))); if (intersection.size > 3) { conflictScore += 0.7; } } const conflictTerms = [ ['use', 'avoid'], ['do', "don't"], ['should', 'never'], ['recommended', 'discouraged'], ['best', 'worst'], ['good', 'bad'], ['correct', 'incorrect'] ]; for (const [positive, negative] of conflictTerms) { if ((content1.includes(positive) && content2.includes(negative)) || (content1.includes(negative) && content2.includes(positive))) { conflictScore += 0.3; } } if (fact1.domain === fact2.domain && fact1.type !== fact2.type) { conflictScore += 0.1; } return Math.min(1, conflictScore); } detectDependency(fact1, fact2) { const content1 = fact1.content.toLowerCase(); const content2 = fact2.content.toLowerCase(); if (fact1.type === 'optimization' && fact2.type === 'verified_pattern') { const words1 = new Set(content1.split(/\s+/)); const words2 = new Set(content2.split(/\s+/)); const intersection = new Set([...words1].filter(x => words2.has(x))); if (intersection.size > 2) { return { relationType: 'extends', dependencyScore: 0.7, reason: 'Optimization builds on verified pattern', }; } } if (fact1.type === 'debugging_solution' && fact2.type === 'anti_pattern') { const words1 = new Set(content1.split(/\s+/)); const words2 = new Set(content2.split(/\s+/)); const intersection = new Set([...words1].filter(x => words2.has(x))); if (intersection.size > 2) { return { relationType: 'prevents', dependencyScore: 0.8, reason: 'Solution prevents anti-pattern', }; } } const requirementPatterns = [ /requires?\s+([^.]+)/gi, /depends?\s+on\s+([^.]+)/gi, /needs?\s+([^.]+)/gi, /prerequisite[:\s]+([^.]+)/gi, ]; for (const pattern of requirementPatterns) { const matches = [...content1.matchAll(pattern)]; for (const match of matches) { const requirement = match[1].toLowerCase(); if (content2.includes(requirement)) { return { relationType: 'requires', dependencyScore: 0.6, reason: 'Explicit dependency mentioned', }; } } } return null; } isValidRelationshipType(type) { return this.relationshipTypes.hasOwnProperty(type); } getRelationshipTypes() { return Object.values(this.relationshipTypes); } async getFactRelationships(factId, includeInverse = true) { const fact = this.factStore.factsIndex.get(factId); if (!fact) { throw new Error(`Fact ${factId} not found`); } const relationships = fact.relationships || []; if (!includeInverse) { return relationships; } const allRelationships = [...relationships]; const allFacts = Array.from(this.factStore.factsIndex.values()); for (const otherFact of allFacts) { if (otherFact.id === factId || !otherFact.relationships) continue; for (const rel of otherFact.relationships) { if (rel.targetId === factId) { allRelationships.push({ ...rel, sourceId: otherFact.id, isInverse: true, }); } } } return allRelationships; } async getRelationshipStats() { const allFacts = Array.from(this.factStore.factsIndex.values()); let totalRelationships = 0; const typeCount = {}; for (const fact of allFacts) { if (fact.relationships) { totalRelationships += fact.relationships.length; for (const rel of fact.relationships) { typeCount[rel.type] = (typeCount[rel.type] || 0) + 1; } } } return { totalRelationships, averageRelationshipsPerFact: allFacts.length > 0 ? totalRelationships / allFacts.length : 0, relationshipsByType: typeCount, factsWithRelationships: allFacts.filter(f => f.relationships && f.relationships.length > 0).length, }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mixelpixx/meMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server