Skip to main content
Glama
contextOverflowPrevention.ts13.3 kB
// ============================================================================ // GLOBAL CONTEXT OVERFLOW PREVENTION UTILITIES // ============================================================================ // Global configuration for context overflow prevention export interface GlobalContextOverflowConfig { maxResponseSize: number; enableTruncation: boolean; maxSummaryLength: number; warningThreshold: number; // Percentage of max size to trigger warnings } // Load configuration from environment variables function loadContextOverflowConfig(): Required<GlobalContextOverflowConfig> { const env = process.env; return { maxResponseSize: parseInt(env.SKYENET_CONTEXT_MAX_RESPONSE_SIZE || '50000', 10), enableTruncation: env.SKYENET_CONTEXT_ENABLE_TRUNCATION !== 'false', maxSummaryLength: parseInt(env.SKYENET_CONTEXT_MAX_SUMMARY_LENGTH || '1000', 10), warningThreshold: parseFloat(env.SKYENET_CONTEXT_WARNING_THRESHOLD || '0.8'), }; } // Default global configuration (fallback values) export const DEFAULT_GLOBAL_CONFIG: Required<GlobalContextOverflowConfig> = { maxResponseSize: 75000, // 75KB response limit (increased for better functionality) enableTruncation: true, maxSummaryLength: 2000, // 2KB summary limit (increased for more context) warningThreshold: 0.85, // Warn at 85% of max size }; // Response monitoring result export interface ResponseMonitoringResult { originalSize: number; finalSize: number; truncated: boolean; warning: boolean; summary?: string; } /** * Global context overflow prevention utility * Applies to all MCP tool responses to prevent context window overflow */ export class GlobalContextOverflowPrevention { private config: Required<GlobalContextOverflowConfig>; constructor(config: Partial<GlobalContextOverflowConfig> = {}) { // Load from environment variables first, then apply any overrides const envConfig = loadContextOverflowConfig(); this.config = { ...envConfig, ...config }; } /** * Monitor and truncate response if necessary * * @param response - The response object to monitor * @param toolName - Name of the tool for logging * @returns Monitoring result with truncation applied if needed */ monitorResponse(response: any, toolName: string, responseMode?: string): { response: any; monitoring: ResponseMonitoringResult } { const responseString = JSON.stringify(response); const originalSize = responseString.length; const monitoring: ResponseMonitoringResult = { originalSize, finalSize: originalSize, truncated: false, warning: false, }; // Apply response mode transformations first (always apply minimal mode regardless of size) let processedResponse = response; if (responseMode === 'minimal') { processedResponse = this.applyMinimalMode(response, toolName); const processedString = JSON.stringify(processedResponse); monitoring.finalSize = processedString.length; monitoring.truncated = originalSize > processedString.length; } // Check if response exceeds size limits const currentSize = JSON.stringify(processedResponse).length; if (currentSize > this.config.maxResponseSize && this.config.enableTruncation) { const truncatedResponse = this.truncateResponse(processedResponse, toolName); const truncatedString = JSON.stringify(truncatedResponse); monitoring.finalSize = truncatedString.length; monitoring.truncated = true; monitoring.summary = this.createTruncationSummary(originalSize, monitoring.finalSize, toolName); console.warn(`Context overflow prevention: Truncated ${toolName} response from ${originalSize} to ${monitoring.finalSize} characters`); return { response: truncatedResponse, monitoring }; } // Check for warning threshold if (currentSize > this.config.maxResponseSize * this.config.warningThreshold) { monitoring.warning = true; console.warn(`Context overflow warning: ${toolName} response size (${currentSize}) approaching limit`); } return { response: processedResponse, monitoring }; } /** * Truncate response intelligently based on type * * @param response - Response to truncate * @param toolName - Name of the tool * @returns Truncated response */ private truncateResponse(response: any, toolName: string): any { // Handle different response types if (response.success && response.data && Array.isArray(response.data.records)) { // Table operation response - truncate records return this.truncateTableResponse(response); } else if (response.success && response.output) { // Background script response - truncate output return this.truncateScriptResponse(response); } else { // Generic response - truncate string content return this.truncateGenericResponse(response); } } /** * Truncate table operation response */ private truncateTableResponse(response: any): any { const maxRecords = Math.max(1, Math.floor(this.config.maxResponseSize / 1000)); // Rough estimate const truncatedRecords = response.data.records.slice(0, maxRecords); return { ...response, data: { ...response.data, records: truncatedRecords, count: truncatedRecords.length, truncated: true, original_count: response.data.records.length, summary: { total_records: response.data.records.length, displayed_records: truncatedRecords.length, truncated: true, message: `Response truncated due to size limits. Showing ${truncatedRecords.length} of ${response.data.records.length} records. Use pagination or field filtering to reduce response size.` } }, metadata: { ...response.metadata, contextOverflowPrevention: true, responseSize: JSON.stringify(response).length, truncated: true } }; } /** * Truncate background script response */ private truncateScriptResponse(response: any): any { const maxOutputLength = this.config.maxSummaryLength; // Ensure text is always a string, never null - prevents bare null serialization const originalText = (response.output && response.output.text != null && typeof response.output.text === 'string') ? response.output.text : ''; const originalHtml = (response.output && response.output.html != null && typeof response.output.html === 'string') ? response.output.html : ''; const truncatedText = originalText.length > maxOutputLength ? originalText.substring(0, maxOutputLength) + '... [TRUNCATED]' : originalText; const truncatedHtml = originalHtml.length > maxOutputLength ? originalHtml.substring(0, maxOutputLength) + '... [TRUNCATED]' : originalHtml; return { ...response, output: { html: truncatedHtml, text: truncatedText, truncated: originalText.length > maxOutputLength, original_size: originalText.length }, metadata: { ...response.metadata, contextOverflowPrevention: true, responseSize: JSON.stringify(response).length, truncated: originalText.length > maxOutputLength } }; } /** * Truncate generic response */ private truncateGenericResponse(response: any): any { const responseString = JSON.stringify(response); const truncatedString = responseString.substring(0, this.config.maxResponseSize) + '... [TRUNCATED]'; try { return JSON.parse(truncatedString); } catch { // If parsing fails, return a safe truncated response return { success: false, error: { code: 'RESPONSE_TOO_LARGE', message: 'Response truncated due to size limits', details: 'Response exceeded maximum size limit and could not be safely truncated' }, metadata: { contextOverflowPrevention: true, truncated: true, originalSize: responseString.length } }; } } /** * Apply minimal mode transformations to reduce response size * * @param response - Response to process * @param toolName - Name of the tool * @returns Processed response with minimal mode applied */ private applyMinimalMode(response: any, toolName: string): any { if (!response.success) { return response; // Don't modify error responses } // Handle different response types if (response.data && Array.isArray(response.data.records)) { // Table operation response return this.applyMinimalModeToTableResponse(response); } else if (response.output) { // Background script response return this.applyMinimalModeToScriptResponse(response); } else if (response.data && response.data.xml_records) { // Update set response return this.applyMinimalModeToUpdateSetResponse(response); } return response; } /** * Apply minimal mode to table operation response */ private applyMinimalModeToTableResponse(response: any): any { const records = response.data.records || []; const largeFields = ['script', 'html', 'css', 'description', 'work_notes', 'comments', 'close_notes', 'resolution_notes']; const processedRecords = records.map((record: any) => { const cleaned: any = {}; Object.keys(record).forEach(key => { if (largeFields.includes(key) && typeof record[key] === 'string' && record[key].length > 100) { cleaned[key] = record[key].substring(0, 100) + '...[truncated]'; } else { cleaned[key] = record[key]; } }); return cleaned; }); // Limit to first 5 records if more than 5 const limitedRecords = processedRecords.length > 5 ? processedRecords.slice(0, 5) : processedRecords; return { ...response, data: { ...response.data, records: limitedRecords, count: limitedRecords.length, summary: records.length > 5 ? `Showing 5 of ${records.length} records. Use pagination for more.` : undefined } }; } /** * Apply minimal mode to background script response */ private applyMinimalModeToScriptResponse(response: any): any { // Ensure output.text is always a string, never null // This prevents bare null from being serialized in JSON responses const safeText = (response.output && response.output.text != null && typeof response.output.text === 'string') ? response.output.text : ''; return { ...response, output: { text: safeText, // Remove HTML in minimal mode }, metadata: { ...response.metadata, htmlSize: 0 } }; } /** * Apply minimal mode to update set response */ private applyMinimalModeToUpdateSetResponse(response: any): any { // Handle update set contents with by_type/by_table structure if (response.data.by_type && response.data.by_table) { // For contents operation, return simplified structure const allRecords: any[] = []; // Flatten all records from by_type structure Object.values(response.data.by_type).forEach((group: any) => { if (group.records) { allRecords.push(...group.records); } }); const limitedRecords = allRecords.length > 5 ? allRecords.slice(0, 5) : allRecords; return { ...response, data: { update_set_sys_id: response.data.update_set_sys_id, update_set_name: response.data.update_set_name, total_count: response.data.total_count, records: limitedRecords, // Simple flat array summary: allRecords.length > 5 ? `Showing 5 of ${allRecords.length} records. Use pagination for more.` : undefined } }; } // Handle recent operation with records if (response.data.records) { const records = response.data.records; const limitedRecords = records.length > 5 ? records.slice(0, 5) : records; return { ...response, data: { ...response.data, records: limitedRecords, summary: records.length > 5 ? `5 of ${records.length} records` : undefined } }; } return response; } /** * Create truncation summary message */ private createTruncationSummary(originalSize: number, finalSize: number, toolName: string): string { const reductionPercent = Math.round(((originalSize - finalSize) / originalSize) * 100); return `Context overflow prevention applied to ${toolName}: reduced response size by ${reductionPercent}% (${originalSize} → ${finalSize} characters)`; } /** * Update configuration */ updateConfig(newConfig: Partial<GlobalContextOverflowConfig>): void { this.config = { ...this.config, ...newConfig }; } /** * Get current configuration */ getConfig(): Required<GlobalContextOverflowConfig> { return { ...this.config }; } } // Global instance for use across all tools export const globalContextOverflowPrevention = new GlobalContextOverflowPrevention();

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/ClearSkye/SkyeNet-MCP-ACE'

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