factory/mcp_server/tools/datasets.py

80 lines
2.7 KiB
Python

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)