Skip to main content
Glama
clpi

CLP MCP - DevOps Infrastructure Server

Official
by clpi
index.ts13.2 kB
import { z } from "zod"; /** * Memory entry schema representing a single memory item */ export const MemoryEntrySchema = z.object({ id: z.string().describe("Unique identifier for the memory entry"), content: z.string().describe("The actual content of the memory"), timestamp: z.number().describe("Unix timestamp when the memory was created"), context: z.string().optional().describe("Context or category for the memory"), tags: z.array(z.string()).default([]).describe("Tags for categorization"), importance: z.number().min(0).max(1).default(0.5).describe("Importance score (0-1)"), accessCount: z.number().default(0).describe("Number of times this memory was accessed"), lastAccessed: z.number().optional().describe("Last access timestamp"), metadata: z.record(z.any()).default({}).describe("Additional metadata"), relatedMemories: z.array(z.string()).default([]).describe("IDs of related memories"), }); export type MemoryEntry = z.infer<typeof MemoryEntrySchema>; /** * Dynamic long-term memory system that provides intelligent storage, * retrieval, and analysis of information over time */ export class LongTermMemory { private memories: Map<string, MemoryEntry> = new Map(); private contextIndex: Map<string, Set<string>> = new Map(); // context -> memory IDs private tagIndex: Map<string, Set<string>> = new Map(); // tag -> memory IDs private timelineIndex: Array<{ timestamp: number; id: string }> = []; /** * Store a new memory entry */ store(params: { content: string; context?: string; tags?: string[]; importance?: number; metadata?: Record<string, any>; }): MemoryEntry { const id = this.generateId(); const timestamp = Date.now(); const memory: MemoryEntry = { id, content: params.content, timestamp, context: params.context, tags: params.tags || [], importance: params.importance || 0.5, accessCount: 0, metadata: params.metadata || {}, relatedMemories: [], }; // Store the memory this.memories.set(id, memory); // Update indices this.updateIndices(memory); // Find and link related memories this.linkRelatedMemories(memory); return memory; } /** * Recall memories based on various criteria */ recall(params: { query?: string; context?: string; tags?: string[]; limit?: number; minImportance?: number; timeRange?: { start?: number; end?: number }; }): MemoryEntry[] { let candidates = Array.from(this.memories.values()); // Filter by context if (params.context && this.contextIndex.has(params.context)) { const contextIds = this.contextIndex.get(params.context)!; candidates = candidates.filter((m) => contextIds.has(m.id)); } // Filter by tags if (params.tags && params.tags.length > 0) { candidates = candidates.filter((m) => params.tags!.some((tag) => m.tags.includes(tag)) ); } // Filter by importance if (params.minImportance !== undefined) { candidates = candidates.filter((m) => m.importance >= params.minImportance!); } // Filter by time range if (params.timeRange) { candidates = candidates.filter((m) => { if (params.timeRange!.start && m.timestamp < params.timeRange!.start) { return false; } if (params.timeRange!.end && m.timestamp > params.timeRange!.end) { return false; } return true; }); } // Search by query if provided if (params.query) { const queryLower = params.query.toLowerCase(); candidates = candidates .map((m) => ({ memory: m, relevance: this.calculateRelevance(m, queryLower), })) .filter((item) => item.relevance > 0) .sort((a, b) => b.relevance - a.relevance) .map((item) => item.memory); } else { // Sort by recency and importance if no query candidates.sort((a, b) => { const scoreA = this.calculateScore(a); const scoreB = this.calculateScore(b); return scoreB - scoreA; }); } // Update access counts const limit = params.limit || 10; const results = candidates.slice(0, limit); results.forEach((m) => { m.accessCount++; m.lastAccessed = Date.now(); }); return results; } /** * Search memories with full-text search */ search(query: string, limit: number = 10): MemoryEntry[] { return this.recall({ query, limit }); } /** * Get memories by context */ getByContext(context: string, limit: number = 10): MemoryEntry[] { return this.recall({ context, limit }); } /** * Get memories by tags */ getByTags(tags: string[], limit: number = 10): MemoryEntry[] { return this.recall({ tags, limit }); } /** * Get recent memories */ getRecent(limit: number = 10): MemoryEntry[] { const sorted = Array.from(this.memories.values()).sort( (a, b) => b.timestamp - a.timestamp ); return sorted.slice(0, limit); } /** * Get important memories */ getImportant(minImportance: number = 0.7, limit: number = 10): MemoryEntry[] { return this.recall({ minImportance, limit }); } /** * Update a memory entry */ update(id: string, updates: Partial<Omit<MemoryEntry, "id" | "timestamp">>): MemoryEntry | null { const memory = this.memories.get(id); if (!memory) return null; // Remove old indices this.removeFromIndices(memory); // Apply updates Object.assign(memory, updates); // Update indices this.updateIndices(memory); return memory; } /** * Delete a memory */ delete(id: string): boolean { const memory = this.memories.get(id); if (!memory) return false; this.removeFromIndices(memory); this.memories.delete(id); // Remove from related memories memory.relatedMemories.forEach((relatedId) => { const related = this.memories.get(relatedId); if (related) { related.relatedMemories = related.relatedMemories.filter( (rid) => rid !== id ); } }); return true; } /** * Get memory statistics */ getStats(): { totalMemories: number; totalContexts: number; totalTags: number; oldestMemory?: number; newestMemory?: number; avgImportance: number; } { const memories = Array.from(this.memories.values()); const timestamps = memories.map((m) => m.timestamp); return { totalMemories: memories.length, totalContexts: this.contextIndex.size, totalTags: this.tagIndex.size, oldestMemory: timestamps.length > 0 ? Math.min(...timestamps) : undefined, newestMemory: timestamps.length > 0 ? Math.max(...timestamps) : undefined, avgImportance: memories.length > 0 ? memories.reduce((sum, m) => sum + m.importance, 0) / memories.length : 0, }; } /** * Consolidate memories - identify patterns and create summaries */ consolidate(context?: string): { patterns: Array<{ pattern: string; count: number; memoryIds: string[] }>; summary: string; } { let memories = Array.from(this.memories.values()); if (context) { const contextIds = this.contextIndex.get(context); if (contextIds) { memories = memories.filter((m) => contextIds.has(m.id)); } } // Find patterns in tags const tagPatterns = new Map<string, string[]>(); memories.forEach((m) => { m.tags.forEach((tag) => { if (!tagPatterns.has(tag)) { tagPatterns.set(tag, []); } tagPatterns.get(tag)!.push(m.id); }); }); const patterns = Array.from(tagPatterns.entries()) .map(([pattern, memoryIds]) => ({ pattern, count: memoryIds.length, memoryIds, })) .filter((p) => p.count > 1) .sort((a, b) => b.count - a.count); // Generate summary const summary = this.generateSummary(memories); return { patterns, summary }; } /** * Export all memories */ export(): MemoryEntry[] { return Array.from(this.memories.values()); } /** * Import memories */ import(memories: MemoryEntry[]): void { memories.forEach((memory) => { this.memories.set(memory.id, memory); this.updateIndices(memory); }); } /** * Clear all memories */ clear(): void { this.memories.clear(); this.contextIndex.clear(); this.tagIndex.clear(); this.timelineIndex = []; } // Private helper methods private generateId(): string { return `mem_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`; } private updateIndices(memory: MemoryEntry): void { // Update context index if (memory.context) { if (!this.contextIndex.has(memory.context)) { this.contextIndex.set(memory.context, new Set()); } this.contextIndex.get(memory.context)!.add(memory.id); } // Update tag index memory.tags.forEach((tag) => { if (!this.tagIndex.has(tag)) { this.tagIndex.set(tag, new Set()); } this.tagIndex.get(tag)!.add(memory.id); }); // Update timeline this.timelineIndex.push({ timestamp: memory.timestamp, id: memory.id }); this.timelineIndex.sort((a, b) => a.timestamp - b.timestamp); } private removeFromIndices(memory: MemoryEntry): void { // Remove from context index if (memory.context) { const contextSet = this.contextIndex.get(memory.context); if (contextSet) { contextSet.delete(memory.id); if (contextSet.size === 0) { this.contextIndex.delete(memory.context); } } } // Remove from tag index memory.tags.forEach((tag) => { const tagSet = this.tagIndex.get(tag); if (tagSet) { tagSet.delete(memory.id); if (tagSet.size === 0) { this.tagIndex.delete(tag); } } }); // Remove from timeline this.timelineIndex = this.timelineIndex.filter((item) => item.id !== memory.id); } private calculateRelevance(memory: MemoryEntry, query: string): number { let score = 0; // Content match const content = memory.content.toLowerCase(); if (content.includes(query)) { score += 1.0; } // Tag match const matchingTags = memory.tags.filter((tag) => tag.toLowerCase().includes(query) ); score += matchingTags.length * 0.5; // Context match if (memory.context && memory.context.toLowerCase().includes(query)) { score += 0.5; } // Boost by importance score *= 1 + memory.importance; // Boost by access frequency score *= 1 + Math.log(memory.accessCount + 1) * 0.1; return score; } private calculateScore(memory: MemoryEntry): number { const recencyWeight = 0.4; const importanceWeight = 0.4; const accessWeight = 0.2; // Recency score (newer is better) const age = Date.now() - memory.timestamp; const recencyScore = Math.exp(-age / (7 * 24 * 60 * 60 * 1000)); // Decay over 1 week // Importance score const importanceScore = memory.importance; // Access score const accessScore = Math.min(memory.accessCount / 10, 1); return ( recencyWeight * recencyScore + importanceWeight * importanceScore + accessWeight * accessScore ); } private linkRelatedMemories(memory: MemoryEntry): void { const candidates = Array.from(this.memories.values()).filter( (m) => m.id !== memory.id ); candidates.forEach((candidate) => { const similarity = this.calculateSimilarity(memory, candidate); if (similarity > 0.5) { // Link if similarity is above threshold if (!memory.relatedMemories.includes(candidate.id)) { memory.relatedMemories.push(candidate.id); } if (!candidate.relatedMemories.includes(memory.id)) { candidate.relatedMemories.push(memory.id); } } }); } private calculateSimilarity(m1: MemoryEntry, m2: MemoryEntry): number { let similarity = 0; // Context similarity if (m1.context && m2.context && m1.context === m2.context) { similarity += 0.5; } // Tag similarity const commonTags = m1.tags.filter((tag) => m2.tags.includes(tag)); if (m1.tags.length > 0 && m2.tags.length > 0) { similarity += (commonTags.length / Math.max(m1.tags.length, m2.tags.length)) * 0.5; } return similarity; } private generateSummary(memories: MemoryEntry[]): string { if (memories.length === 0) { return "No memories to summarize."; } const sortedByImportance = memories .slice() .sort((a, b) => b.importance - a.importance); const topMemories = sortedByImportance.slice(0, 5); const summary = [ `Summary of ${memories.length} memories:`, "", "Most important memories:", ...topMemories.map( (m, i) => `${i + 1}. [${new Date(m.timestamp).toLocaleDateString()}] ${m.content.substring(0, 100)}${m.content.length > 100 ? "..." : ""}` ), ]; return summary.join("\n"); } }

Latest Blog Posts

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/clpi/clp-mcp'

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