Skip to main content
Glama
tool_helpers.py15.7 kB
"""MCP 工具辅助函数库。 此模块提供所有工具模块共享的可复用功能函数, 遵循DRY原则,减少代码重复,提高可维护性。 主要功能: - 参数清理和验证 - 错误响应格式化 - API调用结果处理 - JSON参数解析 - 搜索过滤逻辑 - 日志记录辅助 """ from __future__ import annotations import json import re from typing import Any, Dict, List, Optional, Union from magicapi_tools.logging_config import get_logger from magicapi_tools.utils import error_response logger = get_logger('utils.tool_helpers') # === 参数处理工具 === def clean_string_param(value: Optional[Any], default: Optional[str] = None) -> Optional[str]: """清理字符串参数,处理空值和空白字符。 Args: value: 输入值 default: 默认值 Returns: 清理后的字符串或默认值 """ if value is None: return default if isinstance(value, str): cleaned = value.strip() return cleaned if cleaned else default return str(value).strip() or default def parse_json_param(value: Optional[str], param_name: str = "parameter") -> Optional[Any]: """安全解析JSON参数。 Args: value: JSON字符串 param_name: 参数名称,用于错误信息 Returns: 解析后的对象 Raises: ValueError: JSON格式错误时抛出 """ if value is None or (isinstance(value, str) and not value.strip()): return None if not isinstance(value, str): return value try: return json.loads(value) except json.JSONDecodeError as e: raise ValueError(f"{param_name} 格式错误: {e}") def validate_required_params(params: Dict[str, Any], required: List[str]) -> Optional[Dict[str, Any]]: """验证必需参数。 Args: params: 参数字典 required: 必需参数列表 Returns: 如果验证失败返回错误信息,否则返回None """ missing = [key for key in required if key not in params or params[key] is None] if missing: return error_response("invalid_params", f"缺少必需参数: {', '.join(missing)}") return None # === API调用工具 === def process_api_response(response: Dict[str, Any], operation: str = "operation") -> Dict[str, Any]: """统一处理API响应。 Args: response: HTTP客户端响应 operation: 操作名称,用于日志 Returns: 处理后的结果字典 """ if not response.get("status"): # HTTP请求失败 error_info = response.get("body", {}) logger.error(f"{operation} HTTP请求失败: {error_info.get('message', '未知错误')}") return error_response( error_info.get("code", "http_error"), f"{operation} HTTP请求失败", error_info.get("detail") ) data = response.get("body", {}) if data.get("code") != 1: logger.error(f"{operation} API调用失败: {data.get('message', '未知错误')}") return error_response( data.get("code", -1), f"{operation} API调用失败", data.get("data") ) return {"success": True, "data": data.get("data")} def extract_api_detail_data(payload: Optional[Dict[str, Any]], operation: str = "获取详情") -> Optional[Dict[str, Any]]: """从API详情响应中提取数据。 Args: payload: API响应payload operation: 操作名称 Returns: 提取的数据或None """ if payload is None: logger.warning(f"{operation}: 响应数据为空") return None if not isinstance(payload, dict): logger.warning(f"{operation}: 响应数据格式异常: {type(payload)}") return None return payload # === 错误处理工具 === def create_operation_error(operation: str, error_code: str, message: str, detail: Any = None) -> Dict[str, Any]: """创建操作错误响应。 Args: operation: 操作名称 error_code: 错误代码 message: 错误消息 detail: 详细信息 Returns: 错误响应字典 """ logger.error(f"{operation}失败: {message}") return error_response(error_code, message, detail) def handle_tool_exception(operation: str, exc: Exception) -> Dict[str, Any]: """处理工具执行异常。 Args: operation: 操作名称 exc: 异常对象 Returns: 错误响应字典 """ error_msg = f"{operation}时发生异常: {str(exc)}" logger.error(error_msg, exc_info=True) return error_response("unexpected_error", error_msg, str(exc)) # === 验证工具 === def validate_path_format(path: str, allow_empty: bool = False) -> Optional[str]: """验证路径格式。 Args: path: 路径字符串 allow_empty: 是否允许空路径 Returns: 验证后的路径或None(如果验证失败) """ if not path and not allow_empty: return None if not path: return "" # 确保路径以/开头 if not path.startswith("/"): path = f"/{path}" return path def validate_breakpoints(breakpoints: Any) -> Optional[List[int]]: """验证断点格式。 Args: breakpoints: 断点数据 Returns: 验证后的断点列表或None """ if breakpoints is None or breakpoints == "": return [] if isinstance(breakpoints, str): try: # 尝试解析JSON字符串 parsed = json.loads(breakpoints) if isinstance(parsed, list): return [int(bp) for bp in parsed if isinstance(bp, (int, str)) and str(bp).isdigit()] except (json.JSONDecodeError, ValueError): pass elif isinstance(breakpoints, list): return [int(bp) for bp in breakpoints if isinstance(bp, (int, str)) and str(bp).isdigit()] return [] # === 搜索和过滤工具 === def match_keyword(text: str, keyword: str, case_sensitive: bool = False, exact: bool = False, is_regex: bool = False) -> bool: """检查文本是否匹配关键词。 Args: text: 待检查文本 keyword: 关键词 case_sensitive: 是否区分大小写 exact: 是否精确匹配 is_regex: 是否为正则表达式 Returns: 是否匹配 """ if not text or not keyword: return False if is_regex: flags = 0 if case_sensitive else re.IGNORECASE try: return bool(re.search(keyword, text, flags)) except re.error: return False if exact: if case_sensitive: return keyword == text else: return keyword.lower() == text.lower() # 包含匹配 if case_sensitive: return keyword in text else: return keyword.lower() in text.lower() def apply_limit_and_filter(items: List[Any], limit: int = 10, filters: Optional[List[callable]] = None) -> List[Any]: """应用限制和过滤到项目列表。 Args: items: 项目列表 limit: 最大数量限制 filters: 过滤函数列表 Returns: 过滤和限制后的列表 """ if not items: return [] # 应用过滤器 if filters: for filter_func in filters: items = [item for item in items if filter_func(item)] # 应用数量限制 if limit > 0: items = items[:limit] return items # === 日志工具 === def log_operation_start(operation: str, params: Optional[Dict[str, Any]] = None) -> None: """记录操作开始日志。 Args: operation: 操作名称 params: 操作参数 """ logger.info(f"开始执行: {operation}") if params: # 只记录关键参数,避免敏感信息泄露 safe_params = {k: v for k, v in params.items() if not any(sensitive in k.lower() for sensitive in ['password', 'token', 'secret', 'key'])} logger.debug(f"操作参数: {safe_params}") def log_operation_end(operation: str, success: bool, result_count: Optional[int] = None) -> None: """记录操作结束日志。 Args: operation: 操作名称 success: 是否成功 result_count: 结果数量 """ status = "成功" if success else "失败" if result_count is not None: logger.info(f"{operation} {status}, 返回 {result_count} 条结果") else: logger.info(f"{operation} {status}") def log_api_call_details(operation: str, api_id: Optional[str] = None, api_name: Optional[str] = None, api_path: Optional[str] = None, method: Optional[str] = None, group_id: Optional[str] = None) -> None: """记录API调用详情日志。 Args: operation: 操作类型 api_id: API ID api_name: API名称 api_path: API路径 method: HTTP方法 group_id: 分组ID """ logger.info(f"MCP工具调用: {operation}") if api_id: logger.info(f" API ID: {api_id}") if api_name: logger.info(f" API名称: {api_name}") if api_path: logger.info(f" API路径: {api_path}") if method: logger.info(f" HTTP方法: {method}") if group_id: logger.info(f" 分组ID: {group_id}") # === 通用工具 === def safe_get_nested_value(data: Dict[str, Any], keys: List[str], default: Any = None) -> Any: """安全获取嵌套字典的值。 Args: data: 数据字典 keys: 键路径列表 default: 默认值 Returns: 获取的值或默认值 """ current = data for key in keys: if isinstance(current, dict) and key in current: current = current[key] else: return default return current def format_api_display(method: Optional[str], path: Optional[str], name: Optional[str] = None) -> str: """格式化API显示字符串。 Args: method: HTTP方法 path: 路径 name: 名称 Returns: 格式化的显示字符串 """ display = "" if method and path: display = f"{method} {path}" elif method: display = method elif path: display = path else: display = "未知API" if name and name != path: display += f" [{name}]" return display def calculate_pagination(total: int, page: int, page_size: int) -> Dict[str, Any]: """计算分页信息。 Args: total: 总数量 page: 当前页码 page_size: 每页大小 Returns: 分页信息字典 """ total_pages = (total + page_size - 1) // page_size start_index = (page - 1) * page_size end_index = min(start_index + page_size, total) return { "total": total, "page": page, "page_size": page_size, "total_pages": total_pages, "start_index": start_index, "end_index": end_index, "has_next": page < total_pages, "has_prev": page > 1 } def is_api_response_success(payload: Any, settings) -> bool: """检查API响应是否表示成功。 支持多种响应格式和可配置的状态码/消息。 优先级:message="success" > code检查 > status检查 > 错误字段检查 > 默认成功 Args: payload: API响应数据 settings: MagicAPI配置,包含成功状态码和消息配置 Returns: bool: 是否成功 """ if not isinstance(payload, dict): return False # 🚀 优先级1:检查message字段是否等于"success"(最高优先级) message = payload.get("message") print(f"优先级1:检查message字段是否等于 message: {message}") if message is not None and isinstance(message, str): # 直接匹配"success"字符串(不区分大小写) print(f"优先级1:检查message字段是否等于 message: {message}") if message.strip().lower() == "success": print(f"优先级1:检查message字段是否等于 message: {message}") return True # 如果message不匹配success且包含错误关键字,则失败 error_keywords = ["error", "fail", "exception", "invalid", "wrong", "failed", "not found", "timeout", "denied", "forbidden"] if any(error_keyword in message.lower() for error_keyword in error_keywords): print(f"优先级1:检查message字段是否等于 message: {message}") return False # 🚀 优先级2:检查code字段(可配置的状态码) code = payload.get("code") if code is not None: # 如果code等于配置的成功码,则成功 if code == settings.api_success_code: return True # 如果code不等于成功码,则失败 print(f"优先级2:检查code字段是否等于 code: {code}") return False # 🚀 优先级3:检查status字段(某些自定义格式) status = payload.get("status") if status is not None: if status == settings.api_success_code: return True print(f"优先级3:检查status字段是否等于 status = {settings.api_success_code} : {status}") return False # 🚀 优先级4:检查是否有任何错误相关的字段 error_fields = ["error", "exception", "failure"] for field in error_fields: if field in payload: print(f"优先级4:检查是否有任何错误相关的字段 field: {field}") return False # 🚀 优先级5:默认认为是成功的(兼容模式) # 这样可以兼容一些没有标准格式的API return True def check_api_response_success(payload: Any, settings, operation: str) -> Optional[Dict[str, Any]]: """检查API响应是否成功,如果失败则返回错误响应。 使用 is_api_response_success 进行检查,如果失败则生成错误响应。 Args: payload: API响应数据 settings: MagicAPI配置 operation: 操作名称(用于错误日志) Returns: Optional[Dict[str, Any]]: 如果失败返回错误响应,否则返回None """ if not isinstance(payload, dict): return create_operation_error(operation, "invalid_response", "API返回格式无效", payload) # 使用 is_api_response_success 检查是否成功 if not is_api_response_success(payload, settings): # 确定错误原因和消息 code = payload.get("code") message = payload.get("message", "") status = payload.get("status") if code is not None and code != settings.api_success_code: error_message = message or f"API调用失败,响应码: {code}" return create_operation_error(operation, str(code), error_message, payload) elif message and isinstance(message, str): # message 包含错误关键字 return create_operation_error(operation, "api_error", message, payload) elif status is not None and status != settings.api_success_code: error_message = message or f"API调用失败,状态码: {status}" return create_operation_error(operation, str(status), error_message, payload) else: # 其他错误情况 return create_operation_error(operation, "api_error", "API调用失败", payload) # 如果都没有问题,返回None表示成功 return None

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/Dwsy/magic-api-mcp-server'

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