Skip to main content
Glama

mcp-adr-analysis-server

by tosin2013
output-masking.ts7.8 kB
/** * Output masking middleware for MCP responses * Automatically applies content masking to all tool and resource outputs */ import { McpAdrError } from '../types/index.js'; /** * Configuration for output masking behavior */ export interface MaskingConfig { /** Whether masking is enabled */ enabled: boolean; /** Masking strategy to apply */ strategy: 'full' | 'partial' | 'placeholder' | 'environment'; /** Custom patterns to mask */ customPatterns?: string[]; /** Patterns to skip during masking */ skipPatterns?: string[]; } /** * Default masking configuration */ const DEFAULT_MASKING_CONFIG: MaskingConfig = { enabled: true, strategy: 'partial', customPatterns: [], skipPatterns: [ '[REDACTED]', '[API_KEY_REDACTED]', '[PASSWORD_REDACTED]', '[EMAIL_REDACTED]', '[IP_ADDRESS_REDACTED]', ], }; /** * Apply content masking to MCP response content * * @param response - The MCP response to mask * @param config - Masking configuration to use * @returns Promise resolving to the masked response * @throws McpAdrError if masking fails */ export async function maskMcpResponse( response: any, config: MaskingConfig = DEFAULT_MASKING_CONFIG ): Promise<any> { if (!config.enabled) { return response; } try { // Deep clone the response to avoid modifying the original const maskedResponse = JSON.parse(JSON.stringify(response)); // Apply masking to different response types if (maskedResponse.content && Array.isArray(maskedResponse.content)) { // Tool response with content array for (const contentItem of maskedResponse.content) { if (contentItem.type === 'text' && contentItem.text) { contentItem.text = await maskContent(contentItem.text, config); } } } else if (maskedResponse.contents && Array.isArray(maskedResponse.contents)) { // Resource response with contents array for (const contentItem of maskedResponse.contents) { if (contentItem.text) { contentItem.text = await maskContent(contentItem.text, config); } } } else if (maskedResponse.messages && Array.isArray(maskedResponse.messages)) { // Prompt response with messages array for (const message of maskedResponse.messages) { if (message.content && message.content.text) { message.content.text = await maskContent(message.content.text, config); } } } return maskedResponse; } catch (error) { throw new McpAdrError( `Failed to mask MCP response: ${error instanceof Error ? error.message : String(error)}`, 'MASKING_ERROR' ); } } /** * Apply content masking to a text string */ async function maskContent(content: string, config: MaskingConfig): Promise<string> { try { // Skip if content is already masked if (config.skipPatterns?.some(pattern => content.includes(pattern))) { return content; } // Apply basic masking patterns const { applyBasicMasking } = await import('./content-masking.js'); const strategy = config.strategy === 'environment' ? 'placeholder' : config.strategy; return applyBasicMasking(content, strategy); } catch (error) { // If masking fails, return original content with warning // Log to stderr to avoid corrupting MCP protocol console.error('[WARN] Content masking failed:', error); return content; } } /** * Generate AI-powered masking for sensitive content */ export async function generateAiMasking( content: string, contentType: 'code' | 'documentation' | 'configuration' | 'logs' | 'general' = 'general' ): Promise<{ maskedContent: string; analysisPrompt: string }> { try { const { generateSensitiveContentDetectionPrompt } = await import( '../prompts/security-prompts.js' ); const analysisPrompt = generateSensitiveContentDetectionPrompt(content, contentType); // For now, apply basic masking as fallback const { applyBasicMasking } = await import('./content-masking.js'); const maskedContent = applyBasicMasking(content, 'partial'); return { maskedContent, analysisPrompt: ` # AI-Powered Content Masking Available The following content has been processed with basic masking. For enhanced AI-powered masking, use the analysis prompt below: ## Basic Masked Content \`\`\` ${maskedContent} \`\`\` ## AI Analysis Prompt ${analysisPrompt} ## Instructions 1. Submit the AI analysis prompt to detect sensitive information 2. Use the results with the \`generate_content_masking\` tool for intelligent masking 3. Apply the enhanced masking for better security `, }; } catch (error) { throw new McpAdrError( `Failed to generate AI masking: ${error instanceof Error ? error.message : String(error)}`, 'MASKING_ERROR' ); } } /** * Create masking configuration from environment or defaults */ export function createMaskingConfig(overrides?: Partial<MaskingConfig>): MaskingConfig { const envConfig: Partial<MaskingConfig> = { enabled: process.env['MCP_MASKING_ENABLED'] !== 'false', strategy: (process.env['MCP_MASKING_STRATEGY'] as any) || 'partial', }; return { ...DEFAULT_MASKING_CONFIG, ...envConfig, ...overrides, }; } /** * Validate masking configuration */ export function validateMaskingConfig(config: MaskingConfig): { isValid: boolean; errors: string[]; } { const errors: string[] = []; if (typeof config.enabled !== 'boolean') { errors.push('enabled must be a boolean'); } if (!['full', 'partial', 'placeholder', 'environment'].includes(config.strategy)) { errors.push('strategy must be one of: full, partial, placeholder, environment'); } if (config.customPatterns && !Array.isArray(config.customPatterns)) { errors.push('customPatterns must be an array'); } if (config.skipPatterns && !Array.isArray(config.skipPatterns)) { errors.push('skipPatterns must be an array'); } return { isValid: errors.length === 0, errors, }; } /** * Middleware wrapper for MCP tool responses */ export function withContentMasking<T extends (..._args: any[]) => Promise<any>>( toolFunction: T, config?: MaskingConfig ): T { return (async (...args: any[]) => { const response = await toolFunction(...args); const maskingConfig = config || createMaskingConfig(); return await maskMcpResponse(response, maskingConfig); }) as T; } /** * Apply progressive masking based on content sensitivity */ export async function applyProgressiveMasking( content: string, sensitivityLevel: 'low' | 'medium' | 'high' | 'critical' = 'medium' ): Promise<string> { const strategies: Record<string, 'full' | 'partial' | 'placeholder'> = { low: 'placeholder', medium: 'partial', high: 'full', critical: 'full', }; const strategy = strategies[sensitivityLevel]; const { applyBasicMasking } = await import('./content-masking.js'); return applyBasicMasking(content, strategy); } /** * Detect content sensitivity level using heuristics */ export function detectContentSensitivity(content: string): 'low' | 'medium' | 'high' | 'critical' { const criticalPatterns = [/password/gi, /secret/gi, /private.*key/gi, /api.*key/gi, /token/gi]; const highPatterns = [ /@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, // emails /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, // IP addresses /\b[A-Z0-9]{20,}\b/g, // potential keys/tokens ]; const mediumPatterns = [/localhost/gi, /127\.0\.0\.1/g, /config/gi, /env/gi]; if (criticalPatterns.some(pattern => pattern.test(content))) { return 'critical'; } if (highPatterns.some(pattern => pattern.test(content))) { return 'high'; } if (mediumPatterns.some(pattern => pattern.test(content))) { return 'medium'; } return 'low'; }

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/tosin2013/mcp-adr-analysis-server'

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