前言
随着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':
// 自定义错误类型,继承自基本的McpError
class 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 =