autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
119 lines (118 loc) • 4.67 kB
JavaScript
/**
* MCP 工具统一错误处理
*
* 提供 wrapHandler() 包装函数,将所有 handler 的异常统一转换为
* envelope 格式的错误响应,确保:
* 1. Zod schema 校验 → 结构化 VALIDATION_ERROR (外部输入防御)
* 2. 已知业务错误 → 结构化 errorCode + message
* 3. 未知异常 → 通用 INTERNAL_ERROR + 原始 message
* 4. 一致的 meta.tool + meta.responseTimeMs
*
* @module external/mcp/errorHandler
*/
import { z } from 'zod';
import Logger from '#infra/logging/Logger.js';
import { ConflictError, ConstitutionViolation, NotFoundError, PermissionDenied, ValidationError, } from '#shared/errors/index.js';
import { TOOL_SCHEMAS } from '#shared/schemas/mcp-tools.js';
const logger = Logger.getInstance();
/** 从已知错误类型推断 errorCode */
function inferErrorCode(err) {
if (err instanceof ValidationError) {
return 'VALIDATION_ERROR';
}
if (err instanceof NotFoundError) {
return 'NOT_FOUND';
}
if (err instanceof ConflictError) {
return 'CONFLICT';
}
if (err instanceof PermissionDenied) {
return 'PERMISSION_DENIED';
}
if (err instanceof ConstitutionViolation) {
return 'CONSTITUTION_VIOLATION';
}
const errRecord = err;
if (errRecord.code) {
return errRecord.code;
}
return 'INTERNAL_ERROR';
}
/**
* 包装 MCP handler 函数,提供 Zod 输入校验 + 统一错误处理
*
* 如果 TOOL_SCHEMAS 中存在 toolName 对应的 Zod schema,
* 则在 handler 执行前自动校验并 parse(应用 defaults + coercion),
* 校验失败返回结构化 VALIDATION_ERROR,不会到达 handler。
*
* @param toolName 工具名(用于 meta.tool + schema 查找)
* @param handlerFn 原始 handler: (ctx, args) => Promise<unknown>
* @param [schema] 可选的显式 schema 覆盖(优先于 TOOL_SCHEMAS 自动查找)
* @returns 包装后的 handler,保证 *不会* throw
*/
export function wrapHandler(toolName, handlerFn, schema) {
// 确定使用的 schema:显式传入 > TOOL_SCHEMAS 自动查找
const zodSchema = schema || TOOL_SCHEMAS[toolName];
return async function wrappedHandler(ctx, rawArgs) {
const t0 = Date.now();
try {
// ★ Zod 校验(如果存在 schema),null/undefined 兜底空对象
const safeArgs = rawArgs ?? {};
const args = zodSchema ? zodSchema.parse(safeArgs) : safeArgs;
return await handlerFn(ctx, args);
}
catch (err) {
const elapsed = Date.now() - t0;
// Zod 校验错误 → 结构化 VALIDATION_ERROR
if (err instanceof z.ZodError) {
const details = err.issues
.map((e) => `${e.path.join('.')}: ${e.message}`)
.join('; ');
const msg = `输入校验失败: ${details}`;
logger.warn(`[MCP:${toolName}] VALIDATION_ERROR: ${msg}`);
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
message: msg,
errorCode: 'VALIDATION_ERROR',
meta: { tool: toolName, responseTimeMs: elapsed },
}),
},
],
isError: true,
};
}
// 业务错误 / 未知异常
const errorCode = inferErrorCode(err);
const message = (err instanceof Error ? err.message : '') || 'Unknown error';
const errDetails = err instanceof Error ? err.details : undefined;
logger.error(`[MCP:${toolName}] ${errorCode}: ${message}`, {
tool: toolName,
errorCode,
durationMs: elapsed,
...(errDetails ? { details: errDetails } : {}),
});
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
message,
errorCode,
meta: {
tool: toolName,
responseTimeMs: elapsed,
},
}),
},
],
isError: true,
};
}
};
}
export default { wrapHandler };