Skip to main content
Glama
validation.ts6.03 kB
import { z } from 'zod'; /** * MCP工具参数验证模块 */ // 基础验证schemas export const HeroNameSchema = z.string().min(1, '英雄名称不能为空').max(50, '英雄名称过长'); export const ConfigIdSchema = z.string().min(1, '配置ID不能为空').regex(/^[a-zA-Z0-9_-]+$/, '配置ID格式无效'); export const QuerySchema = z.string().min(5, '查询内容至少需要5个字符').max(2000, '查询内容过长(最多2000字符)'); export const HeroIdsSchema = z.array(z.string()).optional().refine( (arr) => !arr || arr.length <= 10, '最多只能指定10个英雄' ); export const CollaborationModeSchema = z.enum(['parallel', 'sequential', 'intelligent']).optional(); // MCP工具参数schemas export const SummonHeroArgsSchema = z.object({ hero_name: HeroNameSchema }); export const DownloadHeroConfigArgsSchema = z.object({ configId: ConfigIdSchema }); export const StartCollaborationArgsSchema = z.object({ query: QuerySchema, heroIds: HeroIdsSchema, mode: CollaborationModeSchema }); // 验证结果类型 export interface ValidationResult<T> { success: boolean; data?: T; error?: string; details?: string[]; } /** * 通用参数验证函数 */ export function validateArgs<T>(schema: z.ZodSchema<T>, args: unknown): ValidationResult<T> { try { const validatedData = schema.parse(args); return { success: true, data: validatedData }; } catch (error) { if (error instanceof z.ZodError) { const details = error.errors.map(err => `${err.path.join('.')}: ${err.message}`); return { success: false, error: '参数验证失败', details }; } return { success: false, error: '未知验证错误', details: [String(error)] }; } } /** * 创建标准化错误响应 */ export function createErrorResponse(error: string, details?: string[]): { content: Array<{ type: 'text'; text: string }> } { let errorText = `❌ ${error}`; if (details && details.length > 0) { errorText += '\n\n**详细信息**:\n' + details.map(detail => `• ${detail}`).join('\n'); } errorText += '\n\n**解决建议**:\n• 检查参数格式是否正确\n• 参考工具使用说明\n• 确保输入内容符合要求'; return { content: [{ type: 'text', text: errorText }] }; } /** * 工具使用统计接口 */ export interface ToolUsageStats { toolName: string; callCount: number; successCount: number; errorCount: number; lastUsed: string; avgExecutionTime: number; } /** * 工具使用统计管理器 */ export class ToolStatsManager { private stats: Map<string, ToolUsageStats> = new Map(); /** * 记录工具调用 */ recordCall(toolName: string, success: boolean, executionTime: number): void { const existing = this.stats.get(toolName) || { toolName, callCount: 0, successCount: 0, errorCount: 0, lastUsed: new Date().toISOString(), avgExecutionTime: 0 }; existing.callCount++; existing.lastUsed = new Date().toISOString(); if (success) { existing.successCount++; } else { existing.errorCount++; } // 更新平均执行时间 existing.avgExecutionTime = (existing.avgExecutionTime * (existing.callCount - 1) + executionTime) / existing.callCount; this.stats.set(toolName, existing); } /** * 获取工具统计信息 */ getStats(toolName?: string): ToolUsageStats[] { if (toolName) { const stat = this.stats.get(toolName); return stat ? [stat] : []; } return Array.from(this.stats.values()); } /** * 获取统计摘要 */ getSummary(): { totalCalls: number; totalSuccess: number; totalErrors: number; successRate: number; mostUsedTool: string | null; } { const allStats = Array.from(this.stats.values()); const totalCalls = allStats.reduce((sum, stat) => sum + stat.callCount, 0); const totalSuccess = allStats.reduce((sum, stat) => sum + stat.successCount, 0); const totalErrors = allStats.reduce((sum, stat) => sum + stat.errorCount, 0); const successRate = totalCalls > 0 ? totalSuccess / totalCalls : 0; const mostUsedTool = allStats.length > 0 ? allStats.reduce((max, stat) => stat.callCount > max.callCount ? stat : max).toolName : null; return { totalCalls, totalSuccess, totalErrors, successRate, mostUsedTool }; } /** * 重置统计信息 */ reset(): void { this.stats.clear(); } } // 全局统计管理器实例 export const globalToolStats = new ToolStatsManager(); /** * 工具执行装饰器 */ export function withToolStats<T extends any[], R>( toolName: string, fn: (...args: T) => Promise<R> ): (...args: T) => Promise<R> { return async (...args: T): Promise<R> => { const startTime = Date.now(); let success = false; try { const result = await fn(...args); success = true; return result; } catch (error) { success = false; throw error; } finally { const executionTime = Date.now() - startTime; globalToolStats.recordCall(toolName, success, executionTime); } }; } /** * 参数清理工具 */ export function sanitizeArgs(args: any): any { if (typeof args !== 'object' || args === null) { return args; } const sanitized: any = {}; for (const [key, value] of Object.entries(args)) { if (typeof value === 'string') { // 清理字符串:去除多余空格,限制长度 sanitized[key] = value.trim().substring(0, 5000); } else if (Array.isArray(value)) { // 清理数组:限制长度,递归清理元素 sanitized[key] = value.slice(0, 20).map(item => sanitizeArgs(item)); } else if (typeof value === 'object') { // 递归清理对象 sanitized[key] = sanitizeArgs(value); } else { sanitized[key] = value; } } return sanitized; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/juyitingmcp/juyitingmcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server