Skip to main content
Glama
error-detection.tsβ€’6.5 kB
/** * Explicit error detection utilities for MCP responses * * Provides precise error detection logic to avoid false positives * from string-based heuristics and empty/false responses that * are actually valid success states. */ /** * Interface for API response that may contain error information */ interface ApiResponse { error?: unknown; errors?: unknown[]; } export interface ErrorAnalysis { isError: boolean; reason?: | 'null_or_undefined_result' | 'empty_response' | 'explicit_success_false' | 'meaningful_error_object' | 'meaningful_error_string' | 'errors_array_has_items' | 'api_error_array' | 'empty_array'; } /** * Compute error status with explicit detection logic only * * Avoids false positives by only flagging: * - Null/undefined results * - Explicit success: false * - Meaningful error objects with messages * - Non-empty error strings * - Error arrays with actual error items * * @param result - Result object to analyze * @param opts - Optional context for tool-specific logic * @returns Error analysis with explicit reasoning */ export function computeErrorWithContext( result: unknown, opts?: { toolName?: string; httpStatus?: number } ): ErrorAnalysis { const toolName = opts?.toolName; const isPlainObject = result && typeof result === 'object' && !Array.isArray(result); const isEmptyObject = isPlainObject && Object.keys(result as Record<string, unknown>).length === 0; // 1) Always surface explicit API errors first const apiResponse = result as ApiResponse; if ( apiResponse?.error || (Array.isArray(apiResponse?.errors) && apiResponse.errors.length) ) { return { isError: true, reason: 'meaningful_error_object' }; } // Handle arrays (bulk ops, list endpoints, etc.) if (Array.isArray(result)) { // explicit errors inside any element const hasExplicitErr = result.some( (r) => r?.error || (Array.isArray(r?.errors) && r.errors.length) ); if (hasExplicitErr) return { isError: true, reason: 'api_error_array' as const }; // empty arrays are usually a failure for create/bulk flows if (result.length === 0) return { isError: true, reason: 'empty_array' as const }; return { isError: false }; } // 2) Create-record special case: // On 2xx paths the transport may hand minimal shells through; // don't classify {} as an error here β€” let the business layer // (extractor/assert) decide shape. if (toolName === 'create-record') { if (result == null) return { isError: true, reason: 'null_or_undefined_result' }; if (isEmptyObject) return { isError: false }; } // 3) Legacy heuristics (unchanged for other tools) if (result == null) return { isError: true, reason: 'null_or_undefined_result' }; if (isEmptyObject) return { isError: true, reason: 'empty_response' }; // Detect empty objects as errors (per Issue #517 analysis) // Empty objects {} often indicate failed API responses that should be errors // Detect Attio API "unknown" record responses (indicates record not found) // Attio sometimes returns fake records with id.record_id: 'unknown' instead of 404s if (result && typeof result === 'object' && !Array.isArray(result)) { const record = result as Record<string, unknown>; if ( record.id && typeof record.id === 'object' && !Array.isArray(record.id) ) { const id = record.id as Record<string, unknown>; if (id.record_id === 'unknown') { return { isError: true, reason: 'empty_response' }; } } } // Check for explicit success: false if (typeof result === 'object' && result !== null && 'success' in result) { const successField = (result as Record<string, unknown>).success; if (successField === false) { return { isError: true, reason: 'explicit_success_false' }; } } // Check for meaningful error objects if (typeof result === 'object' && result !== null && 'error' in result) { const errorField = (result as Record<string, unknown>).error; // Error object with message if ( errorField && typeof errorField === 'object' && 'message' in errorField ) { const message = (errorField as Record<string, unknown>).message; if (typeof message === 'string' && message.trim()) { return { isError: true, reason: 'meaningful_error_object' }; } } // Error string (non-empty) if (typeof errorField === 'string' && errorField.trim().length > 0) { return { isError: true, reason: 'meaningful_error_string' }; } } // Check for errors array with meaningful items if (typeof result === 'object' && result !== null && 'errors' in result) { const errorsField = (result as Record<string, unknown>).errors; if (Array.isArray(errorsField)) { const hasErrors = errorsField.some((err: unknown) => { if (!err) return false; // Error with message if (typeof err === 'object' && 'message' in err) { const message = (err as Record<string, unknown>).message; return typeof message === 'string' && message.trim(); } // Error with code if (typeof err === 'object' && 'code' in err) { return true; } // Non-empty error string if (typeof err === 'string') { return err.trim().length > 0; } return false; }); if (hasErrors) { return { isError: true, reason: 'errors_array_has_items' }; } } } // Default to no error for all other cases // This includes: // - Empty arrays: [] // - Empty strings: "" // - false/null values in data fields // - Missing fields // - Zero counts return { isError: false }; } /** * Check if a result should have empty error fields removed * * @param result - Result to clean * @returns Cleaned result without empty error indicators */ export function cleanEmptyErrorFields(result: unknown): unknown { if (!result || typeof result !== 'object') { return result; } const cleaned = { ...(result as Record<string, unknown>) }; // Remove empty error field if ('error' in cleaned && (cleaned.error === '' || cleaned.error === null)) { delete cleaned.error; } // Remove empty errors array if ( 'errors' in cleaned && Array.isArray(cleaned.errors) && cleaned.errors.length === 0 ) { delete cleaned.errors; } return cleaned; }

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/kesslerio/attio-mcp-server'

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