Skip to main content
Glama
bradcstevens

Copilot Studio Agent Direct Line MCP Server

by bradcstevens
error-transformer.ts8.92 kB
/** * Error transformation utilities for MCP responses */ import { AxiosError } from 'axios'; import { ApplicationError, AuthenticationError, AuthorizationError, OAuthError, TokenRefreshError, NetworkError, RateLimitError, ServiceUnavailableError, TimeoutError, CircuitBreakerError as CircuitBreakerAppError, ConfigurationError, ValidationError, ErrorCategory, ErrorSeverity, } from '../types/errors.js'; import { CircuitBreakerError } from './circuit-breaker.js'; /** * MCP error response format */ export interface MCPError { code: string; message: string; data?: { severity?: string; retryable?: boolean; recoveryAction?: string; timestamp?: string; [key: string]: unknown; }; } /** * Transform any error to MCP-compatible format * * @param error - Error to transform * @param context - Additional context to include * @returns MCP error object */ export function transformToMCPError(error: unknown, context?: Record<string, unknown>): MCPError { // If it's already an ApplicationError, use its built-in transformation if (error instanceof ApplicationError) { const mcpError = error.toMCPError(); return { ...mcpError, data: { ...(mcpError.data || {}), ...context, }, }; } // Handle CircuitBreakerError from circuit-breaker.ts if (error instanceof CircuitBreakerError) { return { code: ErrorCategory.CIRCUIT_BREAKER, message: 'Service temporarily unavailable due to circuit breaker', data: { severity: ErrorSeverity.HIGH, retryable: false, recoveryAction: 'Wait for circuit breaker to recover before retrying', timestamp: new Date().toISOString(), state: error.state, metrics: error.metrics, ...context, }, }; } // Handle AxiosError (HTTP client errors) if (error instanceof AxiosError) { return transformAxiosError(error, context); } // Handle generic Error if (error instanceof Error) { return { code: ErrorCategory.UNKNOWN, message: error.message || 'An unexpected error occurred', data: { severity: ErrorSeverity.MEDIUM, retryable: false, recoveryAction: 'Contact support if the issue persists', timestamp: new Date().toISOString(), errorName: error.name, ...context, }, }; } // Handle non-Error objects return { code: ErrorCategory.UNKNOWN, message: String(error) || 'An unexpected error occurred', data: { severity: ErrorSeverity.MEDIUM, retryable: false, recoveryAction: 'Contact support if the issue persists', timestamp: new Date().toISOString(), ...context, }, }; } /** * Transform AxiosError to MCP format * * @param error - Axios error * @param context - Additional context * @returns MCP error object */ function transformAxiosError(error: AxiosError, context?: Record<string, unknown>): MCPError { const status = error.response?.status; const statusText = error.response?.statusText; const responseData = error.response?.data as Record<string, unknown> | undefined; // Handle authentication errors (401) if (status === 401) { const authError = new AuthenticationError( `Authentication failed: ${statusText || 'Unauthorized'}`, { status, url: error.config?.url, method: error.config?.method, responseData, } ); return authError.toMCPError(); } // Handle authorization errors (403) if (status === 403) { const authzError = new AuthorizationError( `Authorization failed: ${statusText || 'Forbidden'}`, { status, url: error.config?.url, method: error.config?.method, responseData, } ); return authzError.toMCPError(); } // Handle rate limiting (429) if (status === 429) { const retryAfter = error.response?.headers?.['retry-after']; const retrySeconds = retryAfter ? parseInt(retryAfter, 10) : undefined; const rateLimitError = new RateLimitError( 'Rate limit exceeded', retrySeconds, { status, url: error.config?.url, method: error.config?.method, responseData, } ); return rateLimitError.toMCPError(); } // Handle service unavailable (503) if (status === 503) { const serviceError = new ServiceUnavailableError( `Service unavailable: ${statusText || 'Service Unavailable'}`, { status, url: error.config?.url, method: error.config?.method, responseData, } ); return serviceError.toMCPError(); } // Handle server errors (5xx) if (status && status >= 500) { return { code: ErrorCategory.SERVICE_UNAVAILABLE, message: `Server error: ${statusText || 'Internal Server Error'}`, data: { severity: ErrorSeverity.HIGH, retryable: true, recoveryAction: 'Retry after a brief delay - automatic retries in progress', timestamp: new Date().toISOString(), status, url: error.config?.url, method: error.config?.method, responseData, ...context, }, }; } // Handle network errors (no response) if (!error.response) { const networkError = new NetworkError( error.message || 'Network error occurred', { url: error.config?.url, method: error.config?.method, code: error.code, } ); return networkError.toMCPError(); } // Handle client errors (4xx) if (status && status >= 400) { return { code: ErrorCategory.VALIDATION, message: `Client error: ${statusText || 'Bad Request'}`, data: { severity: ErrorSeverity.LOW, retryable: false, recoveryAction: 'Check request parameters and try again', timestamp: new Date().toISOString(), status, url: error.config?.url, method: error.config?.method, responseData, ...context, }, }; } // Fallback for other errors return { code: ErrorCategory.UNKNOWN, message: error.message || 'HTTP request failed', data: { severity: ErrorSeverity.MEDIUM, retryable: false, recoveryAction: 'Contact support if the issue persists', timestamp: new Date().toISOString(), status, url: error.config?.url, method: error.config?.method, responseData, ...context, }, }; } /** * Transform OAuth-specific errors with enhanced context * * @param error - Error from OAuth flow * @param flowContext - OAuth flow context (auth code, token refresh, etc.) * @returns MCP error object with OAuth context */ export function transformOAuthError( error: unknown, flowContext: { flow: 'authorization' | 'token_exchange' | 'token_refresh'; step?: string; } ): MCPError { // Check if error contains OAuth error information const errorData = error as { error?: string; error_description?: string; error_uri?: string; }; if (errorData.error) { const oauthError = new OAuthError( `OAuth ${flowContext.flow} failed${flowContext.step ? ` at ${flowContext.step}` : ''}`, errorData.error, errorData.error_description, { flow: flowContext.flow, step: flowContext.step, error_uri: errorData.error_uri, } ); return oauthError.toMCPError(); } // Handle token refresh failures specifically if (flowContext.flow === 'token_refresh') { const refreshError = new TokenRefreshError( error instanceof Error ? error.message : 'Token refresh failed', { flow: flowContext.flow, step: flowContext.step, }, error instanceof Error ? error : undefined ); return refreshError.toMCPError(); } // Fall back to general transformation return transformToMCPError(error, flowContext); } /** * Check if an error is retryable based on its MCP representation * * @param mcpError - MCP error object * @returns True if error is retryable */ export function isRetryableMCPError(mcpError: MCPError): boolean { return mcpError.data?.retryable === true; } /** * Extract recovery action from MCP error * * @param mcpError - MCP error object * @returns Recovery action string or undefined */ export function getRecoveryAction(mcpError: MCPError): string | undefined { return mcpError.data?.recoveryAction as string | undefined; } /** * Create a user-friendly error message from MCP error * * @param mcpError - MCP error object * @returns User-friendly message */ export function formatUserMessage(mcpError: MCPError): string { const recoveryAction = getRecoveryAction(mcpError); const baseMessage = mcpError.message; if (recoveryAction) { return `${baseMessage}\n\nSuggested action: ${recoveryAction}`; } return baseMessage; }

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/bradcstevens/copilot-studio-agent-direct-line-mcp'

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