ToolBox MCP Server

by xiaoguomeiyitian
Verified
import fs from 'fs'; import path from 'path'; import { toolsDir } from '../config.js'; import { LogService } from '../logService.js'; interface Tool { name: string; description: string; inputSchema: any; destroy: Function; } interface ToolRequest { params: { name: string; arguments: any; }; } interface ToolResponse { content: Array<{ type: string; text: string; }>; isError?: boolean; } // 全局状态 const tools: Tool[] = []; const handlers: { [key: string]: (request: ToolRequest) => Promise<ToolResponse> } = {}; let isLoaded: boolean = false; /** 清除模块缓存 */ function clearModuleCache(modulePath: string): void { try { // 将文件路径转换为 URL 格式,因为我们使用 'file://' 导入 const moduleUrl = 'file://' + modulePath; // 检查是否在 CommonJS 环境中 // 使用可选链和类型检查来避免在 ES 模块环境中出错 const hasRequire = typeof require !== 'undefined'; if (hasRequire) { try { // 对于 CommonJS 模块 const req = require as any; if (req.cache) { delete req.cache[req.resolve(modulePath)]; } } catch (e) { // 忽略 CommonJS 相关错误 } } // 对于 ES 模块,尝试清除全局缓存 try { // 查找可能的 ESM 缓存键 const cacheKeys = Object.keys(globalThis).filter(key => key.startsWith('__import_meta_resolve__') || key.includes('esm_cache') || key.includes('module_cache') ); for (const key of cacheKeys) { if ((globalThis as any)[key] && typeof (globalThis as any)[key] === 'object') { // 尝试删除模块 URL delete (globalThis as any)[key][moduleUrl]; // 也尝试删除可能的变体 const urlVariants = [ moduleUrl, moduleUrl + '.js', moduleUrl + '.ts', moduleUrl + '.mjs', moduleUrl + '?update=' + Date.now() ]; for (const variant of urlVariants) { if ((globalThis as any)[key][variant]) { delete (globalThis as any)[key][variant]; } } } } } catch (e) { // 忽略 ESM 缓存清理错误 } // 最有效的方法是在导入时添加查询参数 // 这将在 loadToolsInternal 函数中处理 } catch (error) { console.warn(`Failed to clear cache for module ${modulePath}:`, error); } } /** * 加载工具函数,支持初始加载和重新加载 * @param reload 是否为重新加载模式 */ export async function loadTools(reload: boolean = false): Promise<{ [key: string]: (request: ToolRequest) => Promise<ToolResponse> }> { // 如果是初始加载且已加载,则直接返回 if (!reload && isLoaded) return; // 如果是重新加载,则重置状态 if (reload) { for (const tool of tools) { await tool?.destroy?.(); delete handlers[tool.name]; } tools.length = 0; isLoaded = false; } // 获取所有工具文件 const toolFiles = fs.readdirSync(toolsDir).filter(file => file.endsWith('.js') || file.endsWith('.ts')); // 加载每个工具 for (const file of toolFiles) { const toolPath = path.join(toolsDir, file); try { // 如果是重新加载,清除模块缓存 if (reload) clearModuleCache(toolPath); // 导入模块,重新加载时添加时间戳防止缓存 const importPath = 'file://' + toolPath + (reload ? `?update=${Date.now()}` : ''); const { default: tool, schema, destroy } = await import(importPath); const toolName = path.parse(toolPath).name; // 注册工具 tools.push({ name: toolName, description: tool.description, inputSchema: schema, destroy: destroy }); // 注册处理函数 handlers[toolName] = async (request: ToolRequest) => { return await tool(request); }; } catch (error) { console.error(`Failed to ${reload ? 'reload' : 'load'} tool ${file}:`, error); } } isLoaded = true; if (reload) console.log(`Successfully reloaded ${tools.length} tools`); return handlers; } /** 获取工具列表 */ export const listToolsHandler = async () => { return { tools: tools }; }; /* 调用工具的处理函数 */ export const callToolHandler = async (request: ToolRequest, caller: string) => { if (!isLoaded) await loadTools(); const start = Date.now(); const toolName = request.params.name; try { if (handlers[toolName]) { const result = await handlers[toolName](request); await LogService.logAsync({ ts: new Date().toISOString(), tool: toolName, caller: caller, args: request.params.arguments, stat: 'success', cost: Date.now() - start, }); return result; } throw new Error(`Unknown tool: ${toolName}`); } catch (error: any) { await LogService.logAsync({ ts: new Date().toISOString(), tool: toolName, args: request.params.arguments, stat: 'error', err: error.message, trace: error.stack, cost: Date.now() - start, }); return { content: [ { type: "text", text: JSON.stringify(error.message), }, ], isError: true, }; } };