il2cpp-dump-analyzer-mcp
Version:
Agentic RAG system for analyzing IL2CPP dump.cs files from Unity games
1,045 lines • 39.5 kB
JavaScript
"use strict";
/**
* @fileoverview Enhanced MCP Context Management
* Provides context preservation, intelligent caching, and session management
* for complex IL2CPP analysis workflows within the MCP framework
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.MCPContextManager = void 0;
const zlib = __importStar(require("zlib"));
const util_1 = require("util");
const gzip = (0, util_1.promisify)(zlib.gzip);
const gunzip = (0, util_1.promisify)(zlib.gunzip);
/**
* Default configuration for the Context Manager
*/
const DEFAULT_CONFIG = {
maxSessionMemoryMB: 100,
sessionTTLMs: 3600000, // 1 hour
enableCompression: true,
compressionThreshold: 100, // 100 bytes - very low threshold for testing
maxActiveSessions: 10,
cacheConfig: {
maxSizeMB: 50,
ttlMs: 1800000, // 30 minutes
maxEntries: 1000
},
compressionConfig: {
enabled: true,
algorithm: 'gzip',
level: 6,
threshold: 100, // Very low threshold for testing
maxUncompressedMB: 20,
stats: {
totalCompressed: 0,
totalSaved: 0,
averageRatio: 0,
compressionTime: 0,
decompressionTime: 0
}
}
};
/**
* Enhanced MCP Context Management System
* Provides intelligent context preservation and correlation across MCP tool calls
*/
class MCPContextManager {
constructor(context, config = {}) {
this.context = context;
this.config = { ...DEFAULT_CONFIG, ...config };
this.sessions = new Map();
this.globalCorrelation = this.initializeCorrelation();
this.compressionStats = { ...DEFAULT_CONFIG.compressionConfig.stats };
this.context.logger.info('MCP Context Manager initialized', {
config: this.config,
maxSessions: this.config.maxActiveSessions
});
// Start cleanup interval
this.startCleanupInterval();
}
/**
* Create a new analysis session
*/
async createSession(originalRequest, options = {}) {
const sessionId = this.generateSessionId();
const now = Date.now();
const ttl = options.sessionTTLMs || this.config.sessionTTLMs;
// Update compression config if provided
if (options.compressionConfig) {
this.config.compressionConfig = { ...this.config.compressionConfig, ...options.compressionConfig };
}
const session = {
sessionId,
createdAt: now,
lastActivityAt: now,
expiresAt: now + ttl,
originalRequest,
state: 'active',
contextData: new Map(),
executedTools: [],
discoveredEntities: new Map(),
sessionCache: this.initializeSessionCache(),
metrics: this.initializeSessionMetrics(),
preferences: options.preferences
};
// Check session limit
if (this.sessions.size >= this.config.maxActiveSessions) {
await this.evictOldestSession();
}
this.sessions.set(sessionId, session);
this.context.logger.info('Analysis session created', {
sessionId,
originalRequest,
expiresAt: session.expiresAt
});
return session;
}
/**
* Retrieve an existing session
*/
async getSession(sessionId) {
const session = this.sessions.get(sessionId);
if (!session) {
return null;
}
// Check if session is expired
if (Date.now() > session.expiresAt) {
await this.expireSession(sessionId);
return null;
}
return session;
}
/**
* Update session activity timestamp
*/
async updateSessionActivity(sessionId) {
const session = this.sessions.get(sessionId);
if (session) {
session.lastActivityAt = Date.now();
this.context.logger.debug('Session activity updated', { sessionId });
}
}
/**
* Store context data in a session
*/
async storeContext(sessionId, contextData) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(`Session not found: ${sessionId}`);
}
// Check if compression is needed
const dataSize = this.calculateDataSize(contextData.data);
if (this.config.enableCompression && dataSize > this.config.compressionThreshold) {
contextData = await this.compressContextData(contextData);
}
session.contextData.set(contextData.id, contextData);
await this.updateSessionActivity(sessionId);
// Update correlation index
await this.updateCorrelationIndex(sessionId, contextData);
this.context.logger.debug('Context data stored', {
sessionId,
contextId: contextData.id,
type: contextData.type,
compressed: !!contextData.compression?.isCompressed
});
}
/**
* Retrieve context data from a session
*/
async getContext(sessionId, contextId) {
const session = await this.getSession(sessionId);
if (!session) {
return null;
}
let contextData = session.contextData.get(contextId);
if (!contextData) {
return null;
}
// Decompress if needed
if (contextData.compression?.isCompressed) {
contextData = await this.decompressContextData(contextData);
}
// Update access metadata
contextData.metadata.lastAccessedAt = Date.now();
contextData.metadata.accessCount++;
await this.updateSessionActivity(sessionId);
return contextData;
}
/**
* Get all contexts for a session
*/
async getAllContexts(sessionId) {
const session = await this.getSession(sessionId);
if (!session) {
return [];
}
const contexts = [];
for (const contextData of session.contextData.values()) {
const decompressed = contextData.compression?.isCompressed
? await this.decompressContextData(contextData)
: contextData;
contexts.push(decompressed);
}
return contexts;
}
/**
* Update existing context data
*/
async updateContext(sessionId, contextId, newData) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(`Session not found: ${sessionId}`);
}
const existingContext = session.contextData.get(contextId);
if (!existingContext) {
throw new Error(`Context not found: ${contextId}`);
}
// Decompress if needed before updating
let contextData = existingContext.compression?.isCompressed
? await this.decompressContextData(existingContext)
: existingContext;
// Update data and metadata
contextData.data = { ...contextData.data, ...newData };
contextData.metadata.lastAccessedAt = Date.now();
contextData.metadata.accessCount++;
// Recompress if needed
const dataSize = this.calculateDataSize(contextData.data);
if (this.config.enableCompression && dataSize > this.config.compressionThreshold) {
contextData = await this.compressContextData(contextData);
}
session.contextData.set(contextId, contextData);
await this.updateSessionActivity(sessionId);
}
/**
* Cache tool result with intelligent correlation
*/
async cacheToolResult(sessionId, toolResult) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(`Session not found: ${sessionId}`);
}
const cacheKey = this.generateCacheKey(toolResult.toolName, toolResult.parameters);
const semanticTags = this.extractSemanticTags(toolResult);
const cacheEntry = {
key: cacheKey,
data: toolResult.result,
metadata: {
createdAt: Date.now(),
lastAccessedAt: Date.now(),
accessCount: 0,
size: this.calculateDataSize(toolResult.result),
ttl: this.config.cacheConfig.ttlMs
},
semanticTags,
relatedKeys: []
};
// Check cache size limits
if (this.shouldEvictCache(session.sessionCache, cacheEntry)) {
await this.evictCacheEntries(session.sessionCache);
}
session.sessionCache.entries.set(cacheKey, cacheEntry);
session.sessionCache.stats.totalSize += cacheEntry.metadata.size;
// Update correlation index
await this.updateCacheCorrelation(session.sessionCache, cacheEntry);
this.context.logger.debug('Tool result cached', {
sessionId,
toolName: toolResult.toolName,
cacheKey,
semanticTags
});
}
// ============================================================================
// PRIVATE METHODS
// ============================================================================
/**
* Generate unique session ID
*/
generateSessionId() {
return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* Initialize session cache
*/
initializeSessionCache() {
return {
entries: new Map(),
config: {
maxSizeMB: this.config.cacheConfig.maxSizeMB,
ttlMs: this.config.cacheConfig.ttlMs,
maxEntries: 10, // Lower limit for testing
enableSemanticCorrelation: true
},
stats: {
hits: 0,
misses: 0,
evictions: 0,
totalSize: 0,
hitRate: 0
},
correlationIndex: new Map()
};
}
/**
* Initialize session metrics
*/
initializeSessionMetrics() {
return {
totalDuration: 0,
toolsExecuted: 0,
entitiesDiscovered: 0,
cachePerformance: {
hitRate: 0,
averageRetrievalTime: 0,
memoryUsage: 0
},
correlationMetrics: {
successfulCorrelations: 0,
averageCorrelationScore: 0,
recommendationAccuracy: 0
},
memoryMetrics: {
totalMemoryUsed: 0,
compressedDataSize: 0,
compressionRatio: 0,
peakMemoryUsage: 0
},
interactionMetrics: {
averageRequestComplexity: 0,
mostUsedTools: [],
analysisDepth: 0
}
};
}
/**
* Initialize global correlation system
*/
initializeCorrelation() {
return {
entityGraph: new Map(),
toolPatterns: new Map(),
flowRecommendations: [],
semanticIndex: new Map()
};
}
/**
* Get cached tool result
*/
async getCachedResult(sessionId, toolName, parameters) {
const session = await this.getSession(sessionId);
if (!session) {
return null;
}
const cacheKey = this.generateCacheKey(toolName, parameters);
const cacheEntry = session.sessionCache.entries.get(cacheKey);
if (!cacheEntry) {
session.sessionCache.stats.misses++;
// Update hit rate
const totalAccess = session.sessionCache.stats.hits + session.sessionCache.stats.misses;
session.sessionCache.stats.hitRate = session.sessionCache.stats.hits / totalAccess;
return null;
}
// Check TTL
if (Date.now() - cacheEntry.metadata.createdAt > cacheEntry.metadata.ttl) {
session.sessionCache.entries.delete(cacheKey);
session.sessionCache.stats.evictions++;
session.sessionCache.stats.misses++;
// Update hit rate
const totalAccess = session.sessionCache.stats.hits + session.sessionCache.stats.misses;
session.sessionCache.stats.hitRate = session.sessionCache.stats.hits / totalAccess;
return null;
}
// Update access metadata
cacheEntry.metadata.lastAccessedAt = Date.now();
cacheEntry.metadata.accessCount++;
session.sessionCache.stats.hits++;
// Update hit rate
const totalAccess = session.sessionCache.stats.hits + session.sessionCache.stats.misses;
session.sessionCache.stats.hitRate = session.sessionCache.stats.hits / totalAccess;
return { result: cacheEntry.data };
}
/**
* Get cache statistics for a session
*/
async getCacheStats(sessionId) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(`Session not found: ${sessionId}`);
}
return {
...session.sessionCache.stats,
maxSizeMB: session.sessionCache.config.maxSizeMB
};
}
/**
* Find correlations based on search term
*/
async findCorrelations(sessionId, searchTerm) {
const session = await this.getSession(sessionId);
if (!session) {
return [];
}
const correlations = [];
const lowerSearchTerm = searchTerm.toLowerCase();
for (const contextData of session.contextData.values()) {
// Check if context is related to search term
const isRelated = contextData.metadata.tags.some(tag => tag.toLowerCase().includes(lowerSearchTerm)) || JSON.stringify(contextData.data).toLowerCase().includes(lowerSearchTerm);
if (isRelated) {
const decompressed = contextData.compression?.isCompressed
? await this.decompressContextData(contextData)
: contextData;
correlations.push(decompressed);
}
}
return correlations;
}
/**
* Add entity to session
*/
async addEntity(sessionId, entity) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(`Session not found: ${sessionId}`);
}
const entityContext = {
name: entity.name,
type: entity.type,
analysisResults: new Map(),
relationships: entity.relationships?.map(rel => ({
type: rel.type,
target: rel.target,
confidence: rel.confidence || 0.8
})) || [],
discoveredAt: Date.now(),
lastAnalyzedAt: Date.now(),
completeness: 0.1
};
session.discoveredEntities.set(entity.name, entityContext);
session.metrics.entitiesDiscovered = session.discoveredEntities.size;
// Update global entity graph
if (!this.globalCorrelation.entityGraph.has(entity.name)) {
this.globalCorrelation.entityGraph.set(entity.name, new Set());
}
// Add relationships to graph
for (const rel of entityContext.relationships) {
this.globalCorrelation.entityGraph.get(entity.name).add(rel.target);
}
await this.updateSessionActivity(sessionId);
}
/**
* Get entity relationship graph for session
*/
async getEntityGraph(sessionId) {
const session = await this.getSession(sessionId);
if (!session) {
return new Map();
}
const graph = new Map();
for (const [entityName, entityContext] of session.discoveredEntities) {
if (!graph.has(entityName)) {
graph.set(entityName, new Set());
}
for (const relationship of entityContext.relationships) {
graph.get(entityName).add(relationship.target);
}
}
return graph;
}
/**
* Get context-aware recommendations
*/
async getRecommendations(sessionId) {
const session = await this.getSession(sessionId);
if (!session) {
return [];
}
const recommendations = [];
const executedTools = new Set(session.executedTools.map(t => t.toolName));
// Analyze current context to suggest next tools
const hasClassAnalysis = Array.from(session.contextData.values()).some(c => c.metadata.tags.includes('class'));
if (hasClassAnalysis) {
if (!executedTools.has('find_class_hierarchy')) {
recommendations.push({
toolName: 'find_class_hierarchy',
confidence: 0.8,
reasoning: 'Class found, hierarchy analysis would provide inheritance information',
suggestedParameters: {},
expectedBenefit: {
newInformation: 0.7,
contextEnrichment: 0.8,
analysisCompletion: 0.6
},
alternatives: []
});
}
if (!executedTools.has('analyze_dependencies')) {
recommendations.push({
toolName: 'analyze_dependencies',
confidence: 0.7,
reasoning: 'Class analysis complete, dependency analysis would show relationships',
suggestedParameters: {},
expectedBenefit: {
newInformation: 0.6,
contextEnrichment: 0.7,
analysisCompletion: 0.5
},
alternatives: []
});
}
if (!executedTools.has('find_cross_references')) {
recommendations.push({
toolName: 'find_cross_references',
confidence: 0.6,
reasoning: 'Cross-reference analysis would show usage patterns',
suggestedParameters: {},
expectedBenefit: {
newInformation: 0.5,
contextEnrichment: 0.6,
analysisCompletion: 0.4
},
alternatives: []
});
}
}
return recommendations.slice(0, 3); // Return top 3 recommendations
}
/**
* Record tool execution in session
*/
async recordToolExecution(sessionId, execution) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(`Session not found: ${sessionId}`);
}
session.executedTools.push(execution);
session.metrics.toolsExecuted = session.executedTools.length;
// Update tool patterns for correlation
if (session.executedTools.length > 1) {
const previousTool = session.executedTools[session.executedTools.length - 2];
this.updateToolPatterns(previousTool.toolName, execution.toolName);
}
await this.updateSessionActivity(sessionId);
}
/**
* Get tool recommendations based on context
*/
async getToolRecommendations(sessionId) {
const session = await this.getSession(sessionId);
if (!session) {
return [];
}
const recommendations = [];
const recentTools = session.executedTools.slice(-3).map(t => t.toolName);
const toolCounts = new Map();
// Count tool usage to avoid redundancy
for (const tool of session.executedTools) {
toolCounts.set(tool.toolName, (toolCounts.get(tool.toolName) || 0) + 1);
}
// Get last executed tool for context
const lastTool = session.executedTools[session.executedTools.length - 1];
if (!lastTool) {
return [];
}
// Extract entity name from last tool execution
const entityName = this.extractEntityFromParameters(lastTool.parameters);
// Generate recommendations based on last tool
switch (lastTool.toolName) {
case 'search_code':
if ((toolCounts.get('find_class_hierarchy') || 0) === 0) {
recommendations.push({
toolName: 'find_class_hierarchy',
confidence: 0.8,
reasoning: 'Class found, analyze inheritance hierarchy',
suggestedParameters: { class_name: entityName },
expectedBenefit: {
newInformation: 0.8,
contextEnrichment: 0.7,
analysisCompletion: 0.6
},
alternatives: []
});
}
if ((toolCounts.get('analyze_dependencies') || 0) === 0) {
recommendations.push({
toolName: 'analyze_dependencies',
confidence: 0.7,
reasoning: 'Analyze class dependencies and relationships',
suggestedParameters: { class_name: entityName },
expectedBenefit: {
newInformation: 0.7,
contextEnrichment: 0.6,
analysisCompletion: 0.5
},
alternatives: []
});
}
if ((toolCounts.get('find_cross_references') || 0) === 0) {
recommendations.push({
toolName: 'find_cross_references',
confidence: 0.6,
reasoning: 'Find where this class is used',
suggestedParameters: { target_name: entityName, target_type: 'class' },
expectedBenefit: {
newInformation: 0.6,
contextEnrichment: 0.5,
analysisCompletion: 0.4
},
alternatives: []
});
}
break;
case 'find_class_hierarchy':
if ((toolCounts.get('analyze_dependencies') || 0) === 0) {
recommendations.push({
toolName: 'analyze_dependencies',
confidence: 0.8,
reasoning: 'Hierarchy analyzed, now check dependencies',
suggestedParameters: { class_name: entityName },
expectedBenefit: {
newInformation: 0.7,
contextEnrichment: 0.8,
analysisCompletion: 0.6
},
alternatives: []
});
}
break;
}
// Reduce confidence for recently used tools
for (const rec of recommendations) {
const usageCount = toolCounts.get(rec.toolName) || 0;
if (usageCount > 0) {
rec.confidence *= Math.max(0.3, 1 - (usageCount * 0.3));
}
}
return recommendations.slice(0, 3);
}
/**
* Get session metrics
*/
async getSessionMetrics(sessionId) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(`Session not found: ${sessionId}`);
}
// Update metrics
session.metrics.totalDuration = Date.now() - session.createdAt;
session.metrics.cachePerformance.hitRate = session.sessionCache.stats.hitRate;
session.metrics.cachePerformance.memoryUsage = session.sessionCache.stats.totalSize;
// Calculate memory metrics
let totalMemory = 0;
let compressedSize = 0;
for (const context of session.contextData.values()) {
if (context.compression?.isCompressed) {
compressedSize += context.compression.compressedSize;
totalMemory += context.compression.originalSize;
}
else {
const size = this.calculateDataSize(context.data);
totalMemory += size;
}
}
session.metrics.memoryMetrics.totalMemoryUsed = totalMemory;
session.metrics.memoryMetrics.compressedDataSize = compressedSize;
session.metrics.memoryMetrics.compressionRatio = totalMemory > 0 ? compressedSize / totalMemory : 0;
// Update peak memory usage
if (totalMemory > session.metrics.memoryMetrics.peakMemoryUsage) {
session.metrics.memoryMetrics.peakMemoryUsage = totalMemory;
}
return { ...session.metrics };
}
/**
* Get session statistics
*/
async getSessionStats() {
const activeSessions = this.sessions.size;
let totalDuration = 0;
let totalMemory = 0;
for (const session of this.sessions.values()) {
totalDuration += Date.now() - session.createdAt;
// Calculate session memory usage
for (const context of session.contextData.values()) {
if (context.compression?.isCompressed) {
totalMemory += context.compression.compressedSize;
}
else {
totalMemory += this.calculateDataSize(context.data);
}
}
}
return {
activeSessions,
totalSessions: activeSessions, // For now, same as active
averageSessionDuration: activeSessions > 0 ? totalDuration / activeSessions : 0,
memoryUsage: totalMemory
};
}
/**
* Get memory statistics for a session
*/
async getMemoryStats(sessionId) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(`Session not found: ${sessionId}`);
}
return session.metrics.memoryMetrics;
}
/**
* Optimize memory usage for a session
*/
async optimizeMemory(sessionId) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(`Session not found: ${sessionId}`);
}
// Compress uncompressed contexts that exceed threshold
for (const [contextId, contextData] of session.contextData) {
if (!contextData.compression?.isCompressed) {
const size = this.calculateDataSize(contextData.data);
if (size > this.config.compressionThreshold) {
const compressed = await this.compressContextData(contextData);
session.contextData.set(contextId, compressed);
}
}
}
// Evict least recently used contexts if memory limit exceeded
const totalMemory = this.calculateSessionMemoryUsage(session);
if (totalMemory > this.config.maxSessionMemoryMB * 1024 * 1024) {
await this.evictLeastRecentlyUsedContexts(session);
}
this.context.logger.debug('Memory optimization completed', { sessionId });
}
/**
* Get compression configuration for a session
*/
async getCompressionConfig(sessionId) {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error(`Session not found: ${sessionId}`);
}
return this.config.compressionConfig;
}
/**
* Clean up expired sessions
*/
async cleanupExpiredSessions() {
const now = Date.now();
const expiredSessions = [];
for (const [sessionId, session] of this.sessions) {
if (now > session.expiresAt) {
expiredSessions.push(sessionId);
}
}
for (const sessionId of expiredSessions) {
await this.expireSession(sessionId);
}
if (expiredSessions.length > 0) {
this.context.logger.info('Expired sessions cleaned up', {
count: expiredSessions.length
});
}
}
/**
* Clean up all sessions and resources
*/
async cleanup() {
this.sessions.clear();
this.globalCorrelation = this.initializeCorrelation();
this.context.logger.info('Context manager cleanup completed');
}
// ============================================================================
// PRIVATE HELPER METHODS
// ============================================================================
/**
* Calculate data size in bytes
*/
calculateDataSize(data) {
return Buffer.byteLength(JSON.stringify(data), 'utf8');
}
/**
* Compress context data
*/
async compressContextData(contextData) {
const startTime = Date.now();
const originalData = JSON.stringify(contextData.data);
const originalSize = Buffer.byteLength(originalData, 'utf8');
try {
const compressed = await gzip(originalData);
const compressedSize = compressed.length;
// Update compression stats
this.compressionStats.totalCompressed++;
this.compressionStats.totalSaved += (originalSize - compressedSize);
this.compressionStats.compressionTime += (Date.now() - startTime);
this.compressionStats.averageRatio = this.compressionStats.totalSaved /
(this.compressionStats.totalSaved + compressedSize);
return {
...contextData,
data: compressed.toString('base64'),
compression: {
isCompressed: true,
originalSize,
compressedSize,
algorithm: 'gzip'
}
};
}
catch (error) {
this.context.logger.warn('Compression failed, storing uncompressed', {
contextId: contextData.id,
error
});
return contextData;
}
}
/**
* Decompress context data
*/
async decompressContextData(contextData) {
if (!contextData.compression?.isCompressed) {
return contextData;
}
const startTime = Date.now();
try {
const compressedBuffer = Buffer.from(contextData.data, 'base64');
const decompressed = await gunzip(compressedBuffer);
const originalData = JSON.parse(decompressed.toString('utf8'));
// Update decompression stats
this.compressionStats.decompressionTime += (Date.now() - startTime);
return {
...contextData,
data: originalData,
compression: {
...contextData.compression,
isCompressed: false
}
};
}
catch (error) {
this.context.logger.error('Decompression failed', {
contextId: contextData.id,
error
});
throw new Error(`Failed to decompress context data: ${contextData.id}`);
}
}
/**
* Generate cache key for tool and parameters
*/
generateCacheKey(toolName, parameters) {
const paramString = JSON.stringify(parameters, Object.keys(parameters).sort());
return `${toolName}:${Buffer.from(paramString).toString('base64')}`;
}
/**
* Extract semantic tags from tool result
*/
extractSemanticTags(toolResult) {
const tags = [toolResult.toolName];
// Extract tags from parameters
for (const [key, value] of Object.entries(toolResult.parameters)) {
if (typeof value === 'string' && value.length > 0) {
tags.push(value.toLowerCase());
}
}
// Extract tags from result data
if (toolResult.result.data && Array.isArray(toolResult.result.data)) {
for (const item of toolResult.result.data) {
if (item.metadata?.type) {
tags.push(item.metadata.type);
}
}
}
return [...new Set(tags)]; // Remove duplicates
}
/**
* Check if cache should evict entries
*/
shouldEvictCache(cache, newEntry) {
const maxSize = cache.config.maxSizeMB * 1024 * 1024;
const wouldExceedSize = cache.stats.totalSize + newEntry.metadata.size > maxSize;
const wouldExceedCount = cache.entries.size >= cache.config.maxEntries;
return wouldExceedSize || wouldExceedCount;
}
/**
* Evict cache entries using LRU strategy
*/
async evictCacheEntries(cache) {
const entries = Array.from(cache.entries.values());
// Sort by last accessed time (LRU first)
entries.sort((a, b) => a.metadata.lastAccessedAt - b.metadata.lastAccessedAt);
// Remove oldest 25% of entries
const toRemove = Math.ceil(entries.length * 0.25);
for (let i = 0; i < toRemove && i < entries.length; i++) {
const entry = entries[i];
cache.entries.delete(entry.key);
cache.stats.totalSize -= entry.metadata.size;
cache.stats.evictions++;
}
}
/**
* Update cache correlation index
*/
async updateCacheCorrelation(cache, entry) {
// Build correlation based on semantic tags
for (const tag of entry.semanticTags) {
if (!cache.correlationIndex.has(tag)) {
cache.correlationIndex.set(tag, []);
}
cache.correlationIndex.get(tag).push(entry.key);
}
}
/**
* Update correlation index with new context
*/
async updateCorrelationIndex(sessionId, contextData) {
// Update semantic index
for (const tag of contextData.metadata.tags) {
if (!this.globalCorrelation.semanticIndex.has(tag)) {
this.globalCorrelation.semanticIndex.set(tag, []);
}
this.globalCorrelation.semanticIndex.get(tag).push({
entity: contextData.id,
similarity: 1.0,
context: sessionId
});
}
}
/**
* Update tool usage patterns
*/
updateToolPatterns(previousTool, currentTool) {
if (!this.globalCorrelation.toolPatterns.has(previousTool)) {
this.globalCorrelation.toolPatterns.set(previousTool, []);
}
const patterns = this.globalCorrelation.toolPatterns.get(previousTool);
const existing = patterns.find(p => p.followedBy === currentTool);
if (existing) {
existing.frequency++;
existing.confidence = Math.min(0.95, existing.confidence + 0.05);
}
else {
patterns.push({
followedBy: currentTool,
frequency: 1,
confidence: 0.6
});
}
}
/**
* Extract entity name from tool parameters
*/
extractEntityFromParameters(parameters) {
return parameters.class_name ||
parameters.target_name ||
parameters.query ||
parameters.enum_name ||
'Unknown';
}
/**
* Calculate total memory usage for a session
*/
calculateSessionMemoryUsage(session) {
let totalMemory = 0;
for (const context of session.contextData.values()) {
if (context.compression?.isCompressed) {
totalMemory += context.compression.compressedSize;
}
else {
totalMemory += this.calculateDataSize(context.data);
}
}
return totalMemory;
}
/**
* Evict least recently used contexts from session
*/
async evictLeastRecentlyUsedContexts(session) {
const contexts = Array.from(session.contextData.entries());
// Sort by last accessed time
contexts.sort(([, a], [, b]) => a.metadata.lastAccessedAt - b.metadata.lastAccessedAt);
// Remove oldest 20% of contexts
const toRemove = Math.ceil(contexts.length * 0.2);
for (let i = 0; i < toRemove && i < contexts.length; i++) {
const [contextId] = contexts[i];
session.contextData.delete(contextId);
}
this.context.logger.debug('Evicted LRU contexts', {
sessionId: session.sessionId,
evicted: toRemove
});
}
/**
* Expire a session
*/
async expireSession(sessionId) {
const session = this.sessions.get(sessionId);
if (session) {
session.state = 'expired';
this.sessions.delete(sessionId);
this.context.logger.debug('Session expired', { sessionId });
}
}
/**
* Evict oldest session to make room for new one
*/
async evictOldestSession() {
let oldestSession = null;
let oldestSessionId = null;
for (const [sessionId, session] of this.sessions) {
if (!oldestSession || session.createdAt < oldestSession.createdAt) {
oldestSession = session;
oldestSessionId = sessionId;
}
}
if (oldestSessionId) {
await this.expireSession(oldestSessionId);
this.context.logger.info('Evicted oldest session', { sessionId: oldestSessionId });
}
}
/**
* Start cleanup interval for expired sessions
*/
startCleanupInterval() {
setInterval(async () => {
try {
await this.cleanupExpiredSessions();
}
catch (error) {
this.context.logger.error('Cleanup interval error', { error });
}
}, 300000); // Run every 5 minutes
}
}
exports.MCPContextManager = MCPContextManager;
//# sourceMappingURL=mcp-context-manager.js.map