前言
隨著MCP應用的規模和復雜性增長,錯誤處理與日志系統的重要性也日益凸顯。一個健壯的錯誤處理策略和高效的日志系統不僅可以幫助開發者快速定位和解決問題,還能提高應用的可靠性和可維護性。本文作為中級篇的第四篇,將深入探討MCP TypeScript-SDK中的錯誤處理與日志系統,包括健壯錯誤處理策略、結構化日志設計、分布式環境下的日志管理以及監控與警報集成。
在MCP應用開發中,錯誤處理與日志記錄是兩個密不可分的主題。優秀的錯誤處理可以確保應用在遇到意外情況時能夠優雅地降級或恢復,而全面的日志記錄則為問題排查和性能分析提供了必要的信息基礎。通過本文的學習,你將能夠構建一個完善的錯誤處理與日志系統,使你的MCP應用更加可靠和易于維護。
一、健壯錯誤處理策略
MCP TypeScript-SDK提供了多層次的錯誤處理機制,幫助開發者構建健壯的應用。下面我們將從錯誤類型、錯誤捕獲與處理、錯誤傳播以及錯誤恢復與重試四個方面,詳細探討如何在MCP應用中實現健壯的錯誤處理策略。
1.1 MCP錯誤類型層次結構
了解MCP SDK中的錯誤類型層次結構,是實現有效錯誤處理的基礎。MCP提供了一套豐富的錯誤類型,用于表示不同類別的錯誤:
import { McpServer } from '@modelcontextprotocol/sdk';
import { McpError,ValidationError,AuthenticationError,AuthorizationError,ResourceNotFoundError,ParameterValidationError,ToolExecutionError,TransportError,ServerInitializationError,TimeoutError,
} from '@modelcontextprotocol/sdk/errors';// 基本錯誤類型示例
const server = new McpServer({name: 'error-handling-server',description: 'MCP錯誤處理示例服務器',version: '1.0.0',
});// 注冊一個資源,演示不同類型的錯誤
server.registerResource({name: 'error-examples',description: '展示不同類型的錯誤處理',params: {errorType: {type: 'string',enum: ['validation','authentication','authorization','notFound','parameter','tool','transport','timeout','custom',],description: '要模擬的錯誤類型',},},resolve: async (params, context) => {const { errorType } = params;// 根據參數拋出不同類型的錯誤switch (errorType) {case 'validation':throw new ValidationError('輸入數據驗證失敗', {field: 'username',reason: '用戶名必須至少包含3個字符',});case 'authentication':throw new AuthenticationError('身份驗證失敗', {reason: '無效的訪問令牌',});case 'authorization':throw new AuthorizationError('沒有足夠的權限', {requiredPermission: 'admin:read',userPermissions: ['user:read'],});case 'notFound':throw new ResourceNotFoundError('請求的資源不存在', {resourceId: '12345',resourceType: 'user',});case 'parameter':throw new ParameterValidationError('參數驗證失敗', {parameter: 'age',reason: '年齡必須是一個正整數',value: -5,});case 'tool':throw new ToolExecutionError('工具執行失敗', {toolName: 'dataProcessor',reason: '外部API調用超時',});case 'transport':throw new TransportError('傳輸層錯誤', {code: 'CONNECTION_RESET',target: 'http://api.example.com',});case 'timeout':throw new TimeoutError('操作超時', {operation: 'databaseQuery',timeoutMs: 5000,});case 'custom':// 自定義錯誤類型,繼承自基本的McpErrorclass DataCorruptionError extends McpError {constructor(message: string, details?: any) {super('DATA_CORRUPTION', message, details);}}throw new DataCorruptionError('數據損壞錯誤', {dataSource: 'userDatabase',table: 'profiles',corrupted: ['name', 'email'],});default:return {content: '沒有錯誤發生,一切正常',};}},
});
1.2 錯誤捕獲與處理策略
在MCP應用中,錯誤可能發生在各個層面。以下是一種多層次的錯誤捕獲與處理策略:
import { McpServer } from '@modelcontextprotocol/sdk';
import { McpError,createErrorHandler,createErrorMiddleware,
} from '@modelcontextprotocol/sdk/errors';// 創建MCP服務器
const server = new McpServer({name: 'error-handling-demo',description: '錯誤處理策略示例',version: '1.0.0',
});// 1. 全局錯誤處理器
const globalErrorHandler = createErrorHandler({// 默認錯誤處理程序,處理所有其他類型的錯誤default: (error, context) => {// 記錄錯誤console.error(`全局錯誤: ${error.message}`, {errorType: error.type,details: error.details,stack: error.stack,context: {resourceName: context.resourceName,userId: context.auth?.userId,},});// 返回標準化錯誤響應return {error: {type: error.type || 'UNKNOWN_ERROR',message: error.message,code: error instanceof McpError ? error.code : 'INTERNAL_ERROR',},};},// 特定錯誤類型的處理程序handlers: {// 驗證錯誤處理VALIDATION_ERROR: (error, context) => {return {error: {type: 'VALIDATION_ERROR',message: error.message,validationErrors: error.details,},};},// 身份驗證錯誤處理AUTHENTICATION_ERROR: (error, context) => {// 可能需要引導用戶重新登錄return {error: {type: 'AUTHENTICATION_ERROR',message: '請重新登錄以繼續操作',redirectUrl: '/login',},};},// 授權錯誤處理AUTHORIZATION_ERROR: (error, context) => {return {error: {type: 'AUTHORIZATION_ERROR',message: '您沒有執行此操作的權限',requiredPermissions: error.details?.requiredPermission,},};},// 資源未找到錯誤處理RESOURCE_NOT_FOUND: (error, context) => {return {error: {type: 'RESOURCE_NOT_FOUND',message: `找不到請求的資源: ${error.details?.resourceType || '未知'} (ID: ${error.details?.resourceId || '未知'})`,},};},// 超時錯誤處理TIMEOUT_ERROR: (error, context) => {return {error: {type: 'TIMEOUT_ERROR',message: '操作超時,請稍后重試',operation: error.details?.operation,suggestedRetryAfterMs: 5000,},};},},
});// 2. 中間件級別錯誤處理
const errorMiddleware = createErrorMiddleware({onError: async (error, req, context, next) => {// 記錄請求詳情和錯誤console.warn(`請求處理錯誤: ${req.resourceName}`, {params: req.params,error: {type: error.type,message: error.message,details: error.details,},});// 將某些錯誤類型轉換為其他錯誤if (error.type === 'DATABASE_ERROR') {// 轉換為更具體的錯誤類型if (error.details?.code === 'ER_NO_SUCH_TABLE') {return next(new McpError('SYSTEM_CONFIGURATION_ERROR', '系統配置錯誤,請聯系管理員'));}}// 繼續傳遞錯誤到下一個處理程序return next(error);},
});// 3. 資源級別錯誤處理
server.registerResource({name: 'user-profile',description: '用戶個人資料',params: {userId: {type: 'string',description: '用戶ID',},},// 資源級別錯誤處理errorHandler: {// 覆蓋特定錯誤類型的處理RESOURCE_NOT_FOUND: (error, context) => {if (error.details?.resourceType === 'user') {// 提供更具體的、針對此資源的錯誤信息return {error: {type: 'USER_NOT_FOUND',message: `未找到ID為 ${error.details.resourceId} 的用戶`,suggestions: ['檢查用戶ID是否正確','用戶可能已被刪除或禁用',],},};}// 否則使用默認處理return null;},},resolve: async (params, context) => {try {const { userId } = params;const user = await fetchUserProfile(userId);if (!user) {throw new ResourceNotFoundError('用戶不存在', {resourceType: 'user',resourceId: userId,});}return {content: user,};} catch (error) {// 4. 本地錯誤處理(try-catch)if (error.name === 'DatabaseConnectionError') {// 轉換數據庫連接錯誤為MCP錯誤throw new McpError('SERVICE_UNAVAILABLE', '服務暫時不可用,請稍后重試', {originalError: {name: error.name,message: error.message,},});}// 其他錯誤重新拋出,由上層處理throw error;}},
});// 配置全局錯誤處理器
server.useErrorHandler(globalErrorHandler);// 配置錯誤處理中間件
server.useMiddleware(errorMiddleware);// 模擬獲取用戶資料的函數
async function fetchUserProfile(userId) {// 在實際應用中,這里會查詢數據庫const users = {'user-1': { id: 'user-1', name: '張三', email: 'zhang@example.com' },'user-2': { id: 'user-2', name: '李四', email: 'li@example.com' },};return users[userId] || null;
}
1.3 錯誤傳播與聚合
在復雜的MCP應用中,錯誤可能需要在多個組件間傳播,或者需要聚合多個錯誤。下面是一個錯誤傳播與聚合的實現示例:
import { McpServer } from '@modelcontextprotocol/sdk';
import { McpError, AggregateError,ErrorChain,
} from '@modelcontextprotocol/sdk/errors';// 創建MCP服務器
const server = new McpServer({name: 'error-propagation-demo',description: '錯誤傳播與聚合示例',version: '1.0.0',
});// 1. 錯誤鏈(錯誤傳播)
server.registerResource({name: 'data-processor',description: '數據處理器示例',params: {dataId: {type: 'string',description: '要處理的數據ID',},},resolve: async (params, context) => {try {const { dataId } = params;// 調用一系列處理步驟const data = await fetchData(dataId);const processedData = await processData(data);const result = await saveProcessedData(processedData);return {content: result,};} catch (error) {// 創建錯誤鏈,保留錯誤發生的上下文throw new ErrorChain('數據處理失敗', error, {operation: 'data-processor',dataId: params.dataId,timestamp: new Date().toISOString(),});}},
});// 2. 錯誤聚合(多個并行操作)
server.registerResource({name: 'batch-processor',description: '批處理多個操作',params: {items: {type: 'array',items: {type: 'string',},description: '要處理的項目ID列表',},},resolve: async (params, context) => {const { items } = params;// 并行處理多個項目const processingPromises = items.map(itemId => processItem(itemId));// 等待所有處理完成,即使有些會失敗const results = await Promise.allSettled(processingPromises);// 收集成功的結果const successResults = results.filter(result => result.status === 'fulfilled').map(result => (result as PromiseFulfilledResult<any>).value);// 收集錯誤const errors = results.filter(result => result.status === 'rejected').map((result, index) => {const error = (result as PromiseRejectedResult).reason;return new McpError('ITEM_PROCESSING_ERROR',`處理項目 ${items[index]} 失敗: ${error.message}`,{ itemId: items[index], originalError: error });});// 如果有錯誤,創建聚合錯誤if (errors.length > 0) {const errorRate = errors.length / items.length;// 如果錯誤率超過50%,則視為整體失敗if (errorRate > 0.5) {throw new AggregateError('批處理大部分項目失敗',errors,{failedCount: errors.length,totalCount: items.length,errorRate: errorRate,});}// 否則返回部分成功的結果和錯誤信息return {content: successResults,metadata: {successCount: successResults.length,failedCount: errors.length,totalCount: items.length,errors: errors.map(e => ({message: e.message,itemId: e.details.itemId,})),},};}// 所有項目處理成功return {content: successResults,metadata: {successCount: successResults.length,totalCount: items.length,},};},
});// 模擬數據獲取和處理函數
async function fetchData(dataId) {// 模擬可能失敗的數據獲取操作if (dataId === 'invalid') {throw new McpError('DATA_FETCH_ERROR', '數據獲取失敗');}return { id: dataId, raw: '原始數據...' };
}async function processData(data) {// 模擬數據處理if (!data.raw) {throw new McpError('DATA_PROCESSING_ERROR', '無法處理空數據');}return { ...data, processed: '處理后的數據...' };
}async function saveProcessedData(data) {// 模擬數據保存if (data.id === 'unsavable') {throw new McpError('DATA_SAVE_ERROR', '無法保存處理后的數據');}return { ...data, savedAt: new Date().toISOString() };
}// 模擬單個項目處理
async function processItem(itemId) {// 模擬不同的處理結果if (itemId.includes('error')) {throw new Error(`處理 ${itemId} 時發生錯誤`);}// 模擬成功處理return {id: itemId,status: 'processed',timestamp: new Date().toISOString(),};
}
1.4 錯誤恢復與重試策略
處理臨時錯誤(如網絡中斷、服務暫時不可用等)時,重試機制是一種有效的錯誤恢復策略。MCP SDK提供了強大的重試機制:
import { McpServer } from '@modelcontextprotocol/sdk';
import { createRetryPolicy,isRetryableError,withRetry,
} from '@modelcontextprotocol/sdk/errors/retry';// 創建MCP服務器
const server = new McpServer({name: 'retry-demo',description: '錯誤恢復與重試策略示例',version: '1.0.0',
});// 1. 創建重試策略
const retryPolicy = createRetryPolicy({// 最大重試次數maxRetries: 3,// 初始重試延遲(毫秒)initialDelay: 1000,// 延遲增長因子(指數退避)backoffFactor: 2,// 延遲抖動因子,增加隨機性以避免"驚群效應"jitterFactor: 0.2,// 最大延遲時間(毫秒)maxDelay: 30000,// 決定錯誤是否可重試的函數isRetryable: (error) => {// 內置函數檢查常見的可重試錯誤if (isRetryableError(error)) {return true;}// 自定義邏輯,例如特定HTTP狀態碼if (error.details?.statusCode) {const retryableStatusCodes = [429, 503, 504];return retryableStatusCodes.includes(error.details.statusCode);}// 根據錯誤類型判斷return ['CONNECTION_ERROR','TIMEOUT_ERROR','RATE_LIMIT_ERROR','TEMPORARY_SERVER_ERROR',].includes(error.type);},// 重試前的鉤子函數onRetry: (error, attempt, delay, context) => {console.warn(`重試操作 (嘗試 ${attempt}/${retryPolicy.maxRetries})...`, {error: error.message,operation: context.operation,nextRetryDelay: delay,});},
});// 2. 注冊使用重試策略的資源
server.registerResource({name: 'resilient-operation',description: '具有錯誤恢復能力的操作',params: {operation: {type: 'string',enum: ['network-call', 'database-query', 'external-api'],description: '要執行的操作類型',},shouldFail: {type: 'boolean',description: '是否模擬失敗(用于測試)',default: false,},failCount: {type: 'number',description: '模擬連續失敗的次數',default: 1,},},resolve: async (params, context) => {const { operation, shouldFail, failCount } = params;// 使用封裝函數添加重試能力const result =