answer-book-mcp
Version:
智能问答和决策辅助的 MCP (Model Context Protocol) 服务器
423 lines (360 loc) • 10.6 kB
JavaScript
/**
* 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
}
}