UNPKG

answer-book-mcp

Version:

智能问答和决策辅助的 MCP (Model Context Protocol) 服务器

423 lines (360 loc) 10.6 kB
/** * MCP工具基类 * 为所有工具提供统一的基础结构和通用功能 */ import { logger } from '../utils/logger.js' import { sanitizeInput, validateTextSafety } from '../utils/validator.js' /** * 工具基类 */ export class BaseTool { constructor(name, description, inputSchema) { this.name = name this.description = description this.inputSchema = inputSchema this.callCount = 0 this.errorCount = 0 this.lastCalled = null this.averageExecutionTime = 0 } /** * 初始化工具(子类可重写) */ async initialize() { // 默认实现,子类可以重写 logger.debug(`工具 ${this.name} 初始化完成`) } /** * 获取工具定义(MCP格式) */ getDefinition() { return { name: this.name, description: this.description, inputSchema: this.inputSchema } } /** * 执行工具(带性能监控和错误处理) */ async execute(params) { const startTime = Date.now() try { // 记录调用 this.callCount++ this.lastCalled = new Date().toISOString() logger.debug(`工具调用开始: ${this.name}`, { params: this.sanitizeParams(params), callCount: this.callCount }) // 参数验证 const validatedParams = await this.validateParams(params) // 安全检查 await this.securityCheck(validatedParams) // 执行具体逻辑 const result = await this.run(validatedParams) // 结果验证 const validatedResult = await this.validateResult(result) // 更新性能指标 const executionTime = Date.now() - startTime this.updatePerformanceMetrics(executionTime) logger.debug(`工具调用成功: ${this.name}`, { executionTime, resultSize: JSON.stringify(validatedResult).length }) return validatedResult } catch (error) { this.errorCount++ const executionTime = Date.now() - startTime logger.error(`工具调用失败: ${this.name}`, { error: error.message, params: this.sanitizeParams(params), executionTime, errorCount: this.errorCount }) throw this.createToolError(error) } } /** * 具体工具逻辑(子类必须实现) */ async run(params) { throw new Error(`工具 ${this.name} 必须实现 run 方法`) } /** * 参数验证(子类可重写) */ async validateParams(params) { if (!params || typeof params !== 'object') { throw new Error('参数必须是对象') } // 基础参数清理 const cleanParams = {} for (const [key, value] of Object.entries(params)) { if (typeof value === 'string') { cleanParams[key] = sanitizeInput(value) } else { cleanParams[key] = value } } return cleanParams } /** * 安全检查(子类可重写) */ async securityCheck(params) { // 检查文本内容安全性 for (const [key, value] of Object.entries(params)) { if (typeof value === 'string' && !validateTextSafety(value)) { throw new Error(`参数 ${key} 包含不安全内容`) } } // 检查调用频率(防止滥用) if (this.isRateLimited()) { throw new Error('调用频率过高,请稍后再试') } } /** * 结果验证(子类可重写) */ async validateResult(result) { if (result === null || result === undefined) { throw new Error('工具执行结果不能为空') } return result } /** * 创建标准化的工具错误 */ createToolError(originalError) { const toolError = new Error(`工具 ${this.name} 执行失败: ${originalError.message}`) toolError.toolName = this.name toolError.originalError = originalError toolError.timestamp = new Date().toISOString() return toolError } /** * 更新性能指标 */ updatePerformanceMetrics(executionTime) { // 计算平均执行时间 this.averageExecutionTime = (this.averageExecutionTime * (this.callCount - 1) + executionTime) / this.callCount } /** * 检查是否被限流 */ isRateLimited() { // 简单的限流检查:每分钟最多100次调用 const now = Date.now() const oneMinuteAgo = now - 60 * 1000 if (!this.recentCalls) { this.recentCalls = [] } // 清理过期的调用记录 this.recentCalls = this.recentCalls.filter(time => time > oneMinuteAgo) // 添加当前调用 this.recentCalls.push(now) return this.recentCalls.length > 100 } /** * 清理敏感参数用于日志记录 */ sanitizeParams(params) { const sanitized = { ...params } // 移除或脱敏敏感字段 const sensitiveFields = ['password', 'token', 'secret', 'key'] for (const field of sensitiveFields) { if (sanitized[field]) { sanitized[field] = '***' } } // 截断长文本 for (const [key, value] of Object.entries(sanitized)) { if (typeof value === 'string' && value.length > 200) { sanitized[key] = value.substring(0, 200) + '...' } } return sanitized } /** * 获取工具统计信息 */ getStats() { return { name: this.name, callCount: this.callCount, errorCount: this.errorCount, errorRate: this.callCount > 0 ? this.errorCount / this.callCount : 0, averageExecutionTime: this.averageExecutionTime, lastCalled: this.lastCalled, recentCallsCount: this.recentCalls ? this.recentCalls.length : 0 } } /** * 重置统计信息 */ resetStats() { this.callCount = 0 this.errorCount = 0 this.lastCalled = null this.averageExecutionTime = 0 this.recentCalls = [] logger.info(`工具统计信息已重置: ${this.name}`) } /** * 创建成功响应 */ createSuccessResponse(data, message = '操作成功') { return { success: true, message, data, timestamp: new Date().toISOString(), tool: this.name } } /** * 创建错误响应 */ createErrorResponse(error, code = 'TOOL_ERROR') { return { success: false, error: { code, message: error.message, tool: this.name, timestamp: new Date().toISOString() } } } /** * 验证必需参数 */ validateRequiredParams(params, requiredFields) { const missing = [] for (const field of requiredFields) { if (params[field] === undefined || params[field] === null || params[field] === '') { missing.push(field) } } if (missing.length > 0) { throw new Error(`缺少必需参数: ${missing.join(', ')}`) } } /** * 验证参数类型 */ validateParamTypes(params, typeMap) { for (const [field, expectedType] of Object.entries(typeMap)) { if (params[field] !== undefined) { const actualType = typeof params[field] if (actualType !== expectedType) { throw new Error(`参数 ${field} 类型错误,期望 ${expectedType},实际 ${actualType}`) } } } } /** * 验证参数范围 */ validateParamRanges(params, rangeMap) { for (const [field, range] of Object.entries(rangeMap)) { const value = params[field] if (value !== undefined) { if (typeof value === 'number') { if (range.min !== undefined && value < range.min) { throw new Error(`参数 ${field} 不能小于 ${range.min}`) } if (range.max !== undefined && value > range.max) { throw new Error(`参数 ${field} 不能大于 ${range.max}`) } } else if (typeof value === 'string') { if (range.minLength !== undefined && value.length < range.minLength) { throw new Error(`参数 ${field} 长度不能小于 ${range.minLength}`) } if (range.maxLength !== undefined && value.length > range.maxLength) { throw new Error(`参数 ${field} 长度不能大于 ${range.maxLength}`) } } else if (Array.isArray(value)) { if (range.minItems !== undefined && value.length < range.minItems) { throw new Error(`参数 ${field} 项目数不能小于 ${range.minItems}`) } if (range.maxItems !== undefined && value.length > range.maxItems) { throw new Error(`参数 ${field} 项目数不能大于 ${range.maxItems}`) } } } } } /** * 验证枚举值 */ validateEnumParams(params, enumMap) { for (const [field, allowedValues] of Object.entries(enumMap)) { const value = params[field] if (value !== undefined && !allowedValues.includes(value)) { throw new Error(`参数 ${field} 值无效,允许的值: ${allowedValues.join(', ')}`) } } } /** * 异步延迟(用于限流等) */ async delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } /** * 格式化响应数据 */ formatResponse(data, options = {}) { const { includeMetadata = true, includeTimestamp = true, maxDepth = 10 } = options let response = data // 添加元数据 if (includeMetadata) { response = { ...response, _metadata: { tool: this.name, version: '1.0.0', callId: this.generateCallId() } } } // 添加时间戳 if (includeTimestamp) { response._timestamp = new Date().toISOString() } // 限制对象深度(防止循环引用) return this.limitObjectDepth(response, maxDepth) } /** * 生成调用ID */ generateCallId() { return `${this.name}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` } /** * 限制对象深度 */ limitObjectDepth(obj, maxDepth, currentDepth = 0) { if (currentDepth >= maxDepth) { return '[Max Depth Reached]' } if (obj === null || typeof obj !== 'object') { return obj } if (Array.isArray(obj)) { return obj.map(item => this.limitObjectDepth(item, maxDepth, currentDepth + 1)) } const result = {} for (const [key, value] of Object.entries(obj)) { result[key] = this.limitObjectDepth(value, maxDepth, currentDepth + 1) } return result } }