Skip to main content
Glama

Nexus MCP Server

json-validator.ts13 kB
import { JsonRpcValidator, JsonRpcResponse, createResponseValidationMiddleware, } from './json-rpc-validator.js'; import { logger } from './logger.js'; export interface JSONValidationResult { success: boolean; data?: string; error?: string; sanitized?: boolean; } export interface SafeSerializationOptions { fallback?: boolean; sanitize?: boolean; maxDepth?: number; replacer?: (key: string, value: unknown) => unknown; } export class JSONValidator { private static readonly MAX_SAFE_DEPTH = 20; // eslint-disable-next-line no-control-regex private static readonly UNSAFE_CHARS_REGEX = /[\u0000-\u001F\u007F-\u009F]/g; private static readonly TRAILING_COMMA_REGEX = /,(\s*[}\]])/g; private static readonly UNESCAPED_QUOTES_REGEX = /(?<!\\)"/g; /** * Safely serialize an object to JSON with comprehensive error handling */ static safeStringify( value: unknown, options: SafeSerializationOptions = {} ): JSONValidationResult { const { fallback = true, sanitize = true } = options; const startTime = Date.now(); try { // Check if we need fallback serialization by detecting problematic types const needsFallback = this.needsFallbackSerialization(value, 0); if (needsFallback && fallback) { logger.jsonSerialization('serialize', true, { dataType: typeof value, fallbackUsed: true, duration: Date.now() - startTime, }); return this.fallbackSerialization(value); } // First attempt: Standard JSON.stringify with circular reference detection const seen = new WeakSet(); const circularReplacer = (key: string, val: unknown): unknown => { if (options.replacer) { val = options.replacer(key, val); } if (val === null || typeof val !== 'object') { return val; } if (seen.has(val)) { logger.jsonSerialization('serialize', false, { dataType: typeof val, circularRefs: true, error: 'Circular reference detected', duration: Date.now() - startTime, }); if (fallback) { return '[Circular Reference]'; } else { throw new Error('Circular reference detected'); } } seen.add(val); return val; }; let jsonString = JSON.stringify(value, circularReplacer, 2); if (sanitize && jsonString) { const originalLength = jsonString.length; jsonString = this.sanitizeJSON(jsonString); if (jsonString.length !== originalLength) { logger.jsonSerialization('serialize', true, { dataType: typeof value, sanitized: true, dataSize: originalLength, duration: Date.now() - startTime, }); } } // Validate the result with round-trip test const validationResult = this.validateJSON(jsonString); if (!validationResult.success) { logger.jsonSerialization('validate', false, { dataType: typeof value, error: validationResult.error, duration: Date.now() - startTime, }); if (fallback) { return this.fallbackSerialization(value); } return validationResult; } logger.jsonSerialization('serialize', true, { dataType: typeof value, dataSize: jsonString.length, sanitized: sanitize, duration: Date.now() - startTime, }); return { success: true, data: jsonString, sanitized: sanitize, }; } catch (error) { logger.jsonSerialization('serialize', false, { dataType: typeof value, error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime, }); if (fallback) { return this.fallbackSerialization(value); } return { success: false, error: error instanceof Error ? error.message : 'Unknown serialization error', }; } } /** * Check if a value needs fallback serialization */ private static needsFallbackSerialization( value: unknown, depth: number ): boolean { if (depth > this.MAX_SAFE_DEPTH) { return true; } if (value === null || value === undefined) { return false; } if (typeof value === 'function') { return true; } if (typeof value === 'symbol') { return true; } if (value instanceof Error) { return true; } if (Array.isArray(value)) { return value.some(item => this.needsFallbackSerialization(item, depth + 1) ); } if (typeof value === 'object') { // Check for circular references using a simple depth approach try { const seen = new WeakSet(); const checkCircular = (obj: unknown, currentDepth: number): boolean => { if (currentDepth > this.MAX_SAFE_DEPTH) { return true; } if (obj === null || typeof obj !== 'object') { return false; } if (seen.has(obj)) { return true; } seen.add(obj); for (const [_key, val] of Object.entries( obj as Record<string, unknown> )) { if (this.needsFallbackSerialization(val, depth + 1)) { return true; } if ( typeof val === 'object' && val !== null && checkCircular(val, currentDepth + 1) ) { return true; } } return false; }; return checkCircular(value, depth); } catch { return true; } } return false; } /** * Validate JSON string with round-trip testing */ static validateJSON(jsonString: string): JSONValidationResult { if (!jsonString || typeof jsonString !== 'string') { return { success: false, error: 'Invalid input: not a string', }; } try { // Round-trip test: parse and re-stringify const parsed = JSON.parse(jsonString); return { success: true, data: parsed, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'JSON parsing failed', }; } } /** * Sanitize JSON string to remove problematic characters and patterns */ private static sanitizeJSON(jsonString: string): string { let sanitized = jsonString; // Remove unsafe control characters sanitized = sanitized.replace(this.UNSAFE_CHARS_REGEX, ''); // Fix trailing commas sanitized = sanitized.replace(this.TRAILING_COMMA_REGEX, '$1'); // Ensure proper Unicode handling try { // Test if string is valid UTF-8 const buffer = Buffer.from(sanitized, 'utf8'); sanitized = buffer.toString('utf8'); } catch (error) { logger.warn('Unicode sanitization failed', { error }); } return sanitized; } /** * Fallback serialization for problematic objects */ private static fallbackSerialization(value: unknown): JSONValidationResult { try { // Create a safe representation of the object const safeObject = this.createSafeObject(value, 0); const jsonString = JSON.stringify(safeObject, null, 2); return { success: true, data: jsonString, sanitized: true, }; } catch (error) { logger.error('Fallback serialization failed', { error }); // Last resort: return error object as JSON const errorJson = JSON.stringify({ error: 'Serialization failed', message: error instanceof Error ? error.message : String(error), timestamp: new Date().toISOString(), }); return { success: true, data: errorJson, sanitized: true, }; } } /** * Create a safe representation of an object for serialization */ private static createSafeObject(obj: unknown, depth = 0): unknown { if (depth > this.MAX_SAFE_DEPTH) { return '[Max Depth Exceeded]'; } if (obj === null || obj === undefined) { return obj; } if (typeof obj === 'function') { return '[Function]'; } if (typeof obj === 'symbol') { return obj.toString(); } if (typeof obj === 'bigint') { return obj.toString(); } if (obj instanceof Date) { return obj.toISOString(); } if (obj instanceof Error) { return { name: obj.name, message: obj.message, stack: obj.stack, }; } if (Array.isArray(obj)) { return obj.map(item => this.createSafeObject(item, depth + 1)); } if (typeof obj === 'object') { const result: Record<string, unknown> = {}; for (const [key, value] of Object.entries( obj as Record<string, unknown> )) { try { result[key] = this.createSafeObject(value, depth + 1); } catch { result[key] = '[Serialization Error]'; } } return result; } return obj; } /** * Middleware function to wrap MCP responses with JSON validation */ static wrapMCPResponse<T>(response: T): JsonRpcResponse { const startTime = Date.now(); try { // Check if response is already a JSON-RPC message let jsonRpcResponse: unknown; if (response && typeof response === 'object' && 'jsonrpc' in response) { // Already a JSON-RPC response, validate as-is jsonRpcResponse = response; } else { // Create a proper JSON-RPC success response from the result payload jsonRpcResponse = JsonRpcValidator.createSuccessResponse( null, response ); } // Validate JSON-RPC 2.0 compliance const validatedResponse = JsonRpcValidator.validateAndSanitizeResponse(jsonRpcResponse); // Then validate that the response can be serialized const validation = this.safeStringify(validatedResponse, { fallback: false, }); if (!validation.success) { logger.responseValidation('post_serialization', 'failed', { validationErrors: [validation.error || 'Serialization failed'], duration: Date.now() - startTime, responseSize: JSON.stringify(response).length, }); // Return a safe error response instead return JsonRpcValidator.createErrorResponse( null, -32603, 'Internal server error: Response serialization failed', validation.error ); } logger.responseValidation('post_serialization', 'passed', { duration: Date.now() - startTime, responseSize: validation.data?.length || 0, sanitizationApplied: validation.sanitized, }); return validatedResponse; } catch (error) { logger.responseValidation('post_serialization', 'failed', { validationErrors: [ error instanceof Error ? error.message : String(error), ], duration: Date.now() - startTime, }); return JsonRpcValidator.createErrorResponse( null, -32603, 'Internal server error: Response processing failed' ); } } /** * Enhanced MCP response wrapper with pre-transmission validation */ static wrapMCPResponseWithValidation<T>(response: T): JsonRpcResponse { const startTime = Date.now(); const validationMiddleware = createResponseValidationMiddleware(); try { // Apply response validation middleware const validatedResponse = validationMiddleware(response); // Apply JSON validation const finalResponse = this.wrapMCPResponse(validatedResponse); logger.mcpProtocol('response', undefined, { duration: Date.now() - startTime, responseSize: JSON.stringify(finalResponse).length, validationStatus: 'passed', }); return finalResponse; } catch (error) { logger.mcpProtocol('error', undefined, { duration: Date.now() - startTime, error: error instanceof Error ? error.message : String(error), validationStatus: 'failed', }); return JsonRpcValidator.createErrorResponse( null, -32603, 'Internal server error: Response validation failed' ); } } } /** * Utility function for safe JSON stringification */ export function safeStringify( value: unknown, options?: SafeSerializationOptions ): string { const result = JSONValidator.safeStringify(value, options); return result.success && result.data !== undefined ? result.data : '{"error": "Serialization failed"}'; } /** * Utility function for JSON validation */ export function validateJSON(jsonString: string): JSONValidationResult { return JSONValidator.validateJSON(jsonString); }

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/adawalli/nexus'

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