import hashlib from typing import Any from django.core.cache import cache from django.db.models import Q from apps.bi.models import Dataset from apps.bi.services import execute_rendered_dataset, render_dataset_sql from mcp_server.context import authenticated_factory_user, require_permission from mcp_server.tools.common import json_safe_result, validate_result_size def search_datasets(query: str = "", limit: int = 20) -> dict[str, Any]: """搜索可执行的数据集目录,不返回 SQL 等敏感配置。""" authenticated_factory_user() safe_limit = max(1, min(limit, 100)) queryset = Dataset.objects.filter(enabled=True) if query.strip(): queryset = queryset.filter( Q(name__icontains=query.strip()) | Q(code__icontains=query.strip()) | Q(description__icontains=query.strip()) ) rows = queryset.order_by("name", "code", "id").values( "code", "name", "description", "default_param", "test_param", )[:safe_limit] return {"items": list(rows), "limit": safe_limit} def execute_dataset( code: str, parameters: dict[str, Any] | None = None, ) -> dict[str, Any]: """以当前 Factory 用户身份执行启用的数据集。需要 dataset.exec 权限。""" user = authenticated_factory_user() require_permission(user, "dataset.exec") try: dataset = Dataset.objects.get(code=code, enabled=True) except Dataset.DoesNotExist as exc: raise ValueError(f"未找到已启用的数据集:{code}") from exc except Dataset.MultipleObjectsReturned as exc: raise RuntimeError(f"数据集 code 不唯一,无法执行:{code}") from exc query = dict(parameters or {}) query["r_user"] = user.id query["r_dept"] = user.belong_dept_id or "" full_sql = render_dataset_sql(dataset, query) cache_key = None response_data = None if full_sql and dataset.cache_seconds: digest = hashlib.sha256(full_sql.strip(";").encode()).hexdigest() cache_key = f"mcp:dataset:{digest}" response_data = cache.get(cache_key) if response_data is None: response_data, can_cache = execute_rendered_dataset(dataset, full_sql) if cache_key and can_cache and response_data["data"]: cache.set(cache_key, response_data, dataset.cache_seconds) result = json_safe_result( { "code": dataset.code, "name": dataset.name, "description": dataset.description, **response_data, } ) validate_result_size(result) return result def register_dataset_tools(server) -> None: server.tool()(search_datasets) server.tool()(execute_dataset)