Skip to main content
Glama
ivaneve
by ivaneve
README.md
# ThinkingData MCP(Python)

把数数(ThinkingData)Open API 封装成面向 AI 的只读 MCP 服务。项目使用官方 Python MCP SDK 的稳定 `1.x` 版本、FastMCP、httpx 和 Pydantic。

## 已实现能力

- `list_events`:查询项目事件,默认隐藏已隐藏事件。
- `list_properties`:查询事件属性或用户属性。
- `list_reports`:查询已保存报表,可按名称、类型和创建人过滤。
- `event_analysis`:使用简化参数执行事件分析。
- `raw_event_analysis`:安全执行从数数报表复制的原始事件分析 JSON。
- `retention_analysis`:执行初始事件到回访事件的留存分析。
- `query_sql`:执行单条 Trino 只读 SQL,AST 校验后自动添加或收紧 `LIMIT`;兼容数数逐行 JSON 响应。
- `start_paged_sql` / `submit_async_sql`:启动分页或异步只读 SQL 任务。
- `sql_task_status` / `get_sql_result_page` / `cancel_sql_task`:管理 SQL 任务生命周期。
- 元数据内存 TTL 缓存。
- 数数 Token 仅由服务端读取,不出现在 MCP 工具参数或错误信息中。

## 环境要求

- Python 3.11+
- 推荐使用 [uv](https://docs.astral.sh/uv/) 管理环境
- 数数私有化集群地址、项目 ID 和项目级查询密钥

## 本地启动

```bash
cp .env.example .env
# 编辑 .env,填写 THINKINGDATA_BASE_URL、THINKINGDATA_TOKEN、THINKINGDATA_PROJECT_ID

uv sync --extra dev
uv run thinkingdata-mcp
```

默认 Streamable HTTP 地址:

```text
http://127.0.0.1:8000/mcp
```

使用 MCP Inspector 调试:

```bash
npx -y @modelcontextprotocol/inspector
```

如需以 stdio 方式启动:

```bash
MCP_TRANSPORT=stdio uv run thinkingdata-mcp
```

## 配置

| 环境变量 | 默认值 | 说明 |
|---|---:|---|
| `THINKINGDATA_BASE_URL` | 必填 | 数数服务地址,例如 `http://ta2:8992` |
| `THINKINGDATA_TOKEN` | 必填 | 项目级查询密钥 |
| `THINKINGDATA_PROJECT_ID` | 必填 | 数数内部的数字项目 ID(例如 `377`),不是项目 APPID |
| `THINKINGDATA_TIMEOUT_SECONDS` | `30` | 上游请求超时 |
| `THINKINGDATA_VERIFY_SSL` | `true` | 是否验证 HTTPS 证书 |
| `THINKINGDATA_METADATA_TTL_SECONDS` | `600` | 元数据缓存秒数,`0` 表示关闭 |
| `THINKINGDATA_MAX_RESULT_ROWS` | `500` | SQL 和分析结果最大行数 |
| `THINKINGDATA_MAX_SQL_TASK_ROWS` | `100000` | 分页/异步 SQL 的总行数上限 |
| `THINKINGDATA_MAX_SQL_PAGE_SIZE` | `5000` | 分页/异步 SQL 的单页行数上限 |
| `MCP_TRANSPORT` | `streamable-http` | `streamable-http` 或 `stdio` |
| `MCP_HOST` | `127.0.0.1` | HTTP 监听地址 |
| `MCP_PORT` | `8000` | HTTP 监听端口 |

`THINKINGDATA_PROJECT_ID` 对应 Open API 请求中的 `projectId`。它通常是一个正整数;不要填写类似 `2202573b57974fb09e92361bb7519651` 的项目 APPID。可以从数数页面 URL、项目管理页面或让数数管理员查询数字项目 ID。

## 工具示例

事件分析工具接受一个结构化请求:

```json
{
  "request": {
    "event": "login",
    "metric": "TRIG_USER_NUM",
    "start_time": "2026-07-01 00:00:00",
    "end_time": "2026-07-31 23:59:59",
    "granularity": "day",
    "group_by": [{"property": "channel", "table_type": "event"}],
    "filters": [
      {
        "property": "country",
        "operator": "equal",
        "values": ["CN"],
        "table_type": "event"
      }
    ]
  }
}
```

支持的聚合包括 `TOTAL_TIMES`、`TRIG_USER_NUM`、`PER_CAPITA_TIMES`、`SUM`、`AVG`、`MAX`、`MIN`。属性聚合必须提供 `metric_property`。

### 原始报表查询

将数数报表中“复制 API 模型查询条件”得到的 JSON 作为 `payload` 传给
`raw_event_analysis`。服务会拒绝包含 Token、密码或授权字段的请求,并强制覆盖
`projectId`、`limit` 和 `timeoutSeconds`。

### 留存分析

```json
{
  "request": {
    "initial_event": "register",
    "return_event": "app_start",
    "start_time": "2026-07-01 00:00:00",
    "end_time": "2026-07-31 23:59:59",
    "unit_num": 7,
    "granularity": "day",
    "subject_property": "#user_id"
  }
}
```

事件名必须使用数数项目中的真实事件名,可先通过 `list_events` 确认。

### 分页和异步 SQL

1. 使用 `start_paged_sql` 等待查询完成并获取 `taskId`,或使用 `submit_async_sql`
   立即提交。
2. 通过 `sql_task_status` 查询任务,直到状态为 `FINISHED`。
3. 通过 `get_sql_result_page` 从 `page_id=0` 开始逐页下载。
4. 不再需要的运行中任务可通过 `cancel_sql_task` 取消。

分页和异步 SQL 同样只允许单条 `SELECT`/`WITH`,并会强制总行数 `LIMIT`。数数要求
`page_size` 至少为 `1000`。

## 安全边界

- 优先配置项目级 Token,不使用 root Token。
- `query_sql` 只接受一条 `SELECT`/`WITH` 查询,并拒绝 DDL、DML 和动态 `LIMIT`。
- 分页和异步 SQL 使用独立的总行数上限,但仍执行同样的只读 AST 校验。
- `raw_event_analysis` 不允许客户端指定项目或传入密钥。
- 服务会强制结果行数上限,但生产环境仍应在网关增加访问认证、请求限流和审计。
- 数数要求 Token 出现在 URL Query;反向代理和访问日志必须配置查询参数脱敏。
- 不要把 `.env` 提交到 Git。
- SQL 白名单目前依赖项目级 Token 隔离;如果一个 Token 能跨项目访问,应在投入生产前增加表级白名单。

## 测试与检查

```bash
uv run pytest
uv run ruff check .
uv run mypy
```

## Docker

```bash
docker build -t thinkingdata-mcp .
docker run --rm -p 8000:8000 --env-file .env \
  -e MCP_HOST=0.0.0.0 thinkingdata-mcp
```