Skip to main content
Glama
XuYui

admission-feishu-mcp

by XuYui
README.md
# 录取数据飞书多维表格 MCP

这个 MCP 给 AstrBot 提供招生录取数据查询、聚合和跨表对比能力。机器人只拿到 MCP 处理后的统计结果,不需要把多维表格大表原始数据塞进上下文。

示例配置支持 5 个数据集:

- `admission_2026`:26录取数据表
- `admission_2021_2025`:21-25录取数据表
- `plan_2025`:25计划表
- `plan_2026`:26计划表
- `score_rank_2026_primary`:26一分一段表

核心原则:

- 字段来自飞书表格实时字段列表,字段写错会直接报错并给相近字段建议。
- 每张表有自己的字段别名映射,例如 `school` 在 26 录取表对应 `匹配中学`,在 21-25 录取表对应 `中学`。
- 聚合工具会扫描完飞书返回的所有匹配记录后再返回;如果飞书接口失败,不返回半截统计。
- 数值聚合用 Decimal 计算,结果以字符串返回,避免浮点误差。
- 默认不允许返回敏感字段;如内部使用可设置 `ADMISSION_MCP_ALLOW_SENSITIVE_FIELDS=true`。
- 所有工具都要求由 AstrBot 飞书事件上下文注入 `requester_id`,服务端据此检查显式读取、写入、运维和特定能力权限。

## 1. 配置

`.env` 至少需要:

~~~text
FEISHU_APP_ID=cli_xxx
FEISHU_APP_SECRET=xxx
FEISHU_APP_TOKEN=base_or_bitable_token
ADMISSION_MCP_ALLOW_SENSITIVE_FIELDS=false
~~~

先复制公开仓库中的示例文件,再填入你自己的飞书配置:

~~~bash
cp .env.example .env
cp config/datasets.example.json config/datasets.json
cp admission-permissions.example.json admission-permissions.json
~~~

上述三个运行时文件已被 `.gitignore` 排除,请勿提交真实凭据、表格 ID、人员姓名或 Open ID。

四张表的 `table_id`、`view_id` 和字段别名放在:

~~~text
config/datasets.json
~~~

如果配置文件不在默认位置,可以设置:

~~~text
ADMISSION_DATASETS_CONFIG=/path/to/datasets.json
~~~

权限由 `admission-permissions.json` 和 `admission-tool-permissions.json` 管理。系统仅保留三种招办角色,实际读、写和运维权限以人员配置中的显式开关为准,详见 [PERMISSIONS.md](PERMISSIONS.md)。

## 2. 启动 MCP

本服务使用 MCP stdio 方式:

~~~powershell
python -m pip install -r requirements.txt
python run_mcp.py
~~~

AstrBot 的 MCP 配置形态类似:

~~~json
{
  "mcpServers": {
    "admission-feishu": {
      "command": "python3",
      "args": ["/AstrBot/data/mcp/admission-feishu/run_mcp.py"],
      "active": true
    }
  }
}
~~~

## 3. 工具

### admission_config_check

检查配置和数据集状态。`ping_feishu=true` 时会对每个数据集做最小只读查询。

### admission_schema

查看指定数据集的字段、别名、敏感字段和支持操作。

示例:

~~~json
{"requester_id": "ou_from_event_context", "dataset": "admission_2021_2025"}
~~~

### admission_send_student_photo

按姓名或学号精确查询 `admission_2026` 的学生记录,并将“个人照片”附件通过机器人私聊发送给当前查询人。图片只在内存中完成多维表格素材下载和飞书 IM 图片上传,不写入本地临时文件。姓名存在重名时不会发送,需改用学号。

示例:

~~~json
{
  "requester_id": "ou_from_event_context",
  "identifier": "202600001"
}
~~~

### admission_query

查询少量明细。`dataset` 留空时默认查 `admission_2026`,兼容旧用法。

示例:

~~~json
{
  "requester_id": "ou_from_event_context",
  "dataset": "admission_2021_2025",
  "filters": [
    {"field": "year", "op": "eq", "value": 2025},
    {"field": "school", "op": "contains", "value": "南通中学"}
  ],
  "fields": ["year", "province", "school", "major", "score", "rank"],
  "limit": 20
}
~~~

### admission_aggregate

单表聚合,适合人数、分布、最高/最低/平均分、计划数。

示例:查 2025 年南通中学录取人数和分数统计:

~~~json
{
  "requester_id": "ou_from_event_context",
  "dataset": "admission_2021_2025",
  "filters": [
    {"field": "year", "op": "eq", "value": 2025},
    {"field": "school", "op": "contains", "value": "南通中学"}
  ],
  "metrics": [
    {"name": "人数", "op": "count"},
    {"name": "最低投档成绩", "op": "min", "field": "score"},
    {"name": "最高投档成绩", "op": "max", "field": "score"},
    {"name": "平均投档成绩", "op": "avg", "field": "score"}
  ]
}
~~~

计划表如果不传 `metrics`,默认按 `PlanNUM` 汇总为 `计划数`。

### admission_zodiac_distribution

按指定年份统计十二星座录取人数。星座由 MCP 在内部使用“出生月份”和“出生日”计算,只返回汇总人数和无效日期数量,不返回生日明细。可通过 `filters` 继续限定省份、中学、专业或书院。

~~~json
{
  "requester_id": "ou_from_event_context",
  "year": 2026,
  "filters": [
    {"field": "province", "op": "eq", "value": "江苏"}
  ]
}
~~~

### admission_score_rank

查询 2026 年某省某分数对应的一分一段位次区间。`province` 和 `score` 必填,`subject` 可选;科类名称按省份考试模式区分:新疆返回“文史/理工”,新高考省份返回“历史类/物理类”,综合改革省份返回“综合改革”。工具会按省份自动转换“文科”“理科”等简称。

~~~json
{
  "requester_id": "ou_from_event_context",
  "province": "江苏",
  "score": 650,
  "subject": "物理类"
}
~~~

### admission_college_rank

计算指定年份目标书院的平均投档成绩排名。MCP 会在内部使用全部书院数据完成排名,但只返回目标书院的平均分、名次、参评书院数和样本量,不返回其他书院的名称或统计值。调用者需要显式配置 `benchmark.college_rank` 能力。

### admission_multi_aggregate

一次执行多个聚合请求,适合同时取多个年份或计划/录取数据。

### admission_compare

跨表对比工具。每个 request 指定 `name`、`dataset`、`filters`;公共 `group_by` 和 `metrics` 会按各表别名自动映射。

示例:比较南通中学 2025 年和 2024 年录取情况:

~~~json
{
  "requester_id": "ou_from_event_context",
  "requests": [
    {
      "name": "2025",
      "dataset": "admission_2021_2025",
      "filters": [
        {"field": "year", "op": "eq", "value": 2025},
        {"field": "school", "op": "contains", "value": "南通中学"}
      ]
    },
    {
      "name": "2024",
      "dataset": "admission_2021_2025",
      "filters": [
        {"field": "year", "op": "eq", "value": 2024},
        {"field": "school", "op": "contains", "value": "南通中学"}
      ]
    }
  ],
  "metrics": [
    {"name": "人数", "op": "count"},
    {"name": "最低投档成绩", "op": "min", "field": "score"},
    {"name": "最高投档成绩", "op": "max", "field": "score"},
    {"name": "平均投档成绩", "op": "avg", "field": "score"}
  ]
}
~~~

### admission_yearly_score_trend

年度分数趋势专用工具。适合“21-26 各年最低分/分数线分别是多少”这类问题;内部最多扫描 21-25 历史表一次、26 录取表一次,避免逐年多次调用。

示例:江苏省 2021-2026 年各年最低投档成绩:

~~~json
{
  "requester_id": "ou_from_event_context",
  "start_year": 2021,
  "end_year": 2026,
  "province": "江苏"
}
~~~

不要自动加 `score_gt` 或 `score_gte`;只有用户明确要求排除某类分数时才使用。

### admission_permission_check

查看当前请求者自身的身份、显式读写权限和 MCP 运维权限。

### admission_update_record

按 `record_id` 更新单条飞书记录。只有显式配置 `write_all: true` 的用户可调用,角色名称本身不授予写权限,且只能修改字段白名单中的字段。

## 4. 常用别名

录取表常用:

- `year`:录取年份
- `province`:省市名称
- `city`:地级市
- `school`:26 表为匹配中学,21-25 表为中学
- `major`:录取专业
- `major_category`:录取大类,仅 26 表有
- `subject`:科类名称
- `score`:投档成绩
- `rank`:省排位

计划表常用:

- `year`:YEAR 或 Year
- `province`:SYSSMC
- `major`:ZSZYMC
- `subject`:KLMC
- `plan_count`:PlanNUM
- `category`:JHLBMC
- `direction`:ZKFX
- `major_group`:XBZYDH

## 5. 验证

不连接飞书时先跑:

~~~powershell
python -m unittest discover -s tests -v
~~~

部署后建议调用:

~~~json
{"tool": "admission_config_check", "arguments": {"requester_id": "ou_from_event_context", "ping_feishu": true}}
~~~

再用一个人工已知答案的小范围问题核对,例如“2025 年南通中学录取人数”和多维表格界面筛选结果是否一致。

AstrBot 侧还需要启用 `plugins/admission_access_control`,确保每次招生 MCP 调用的 `requester_id` 都由飞书事件发送者 ID 强制覆盖。

## 6. 安全与许可证

仓库只提供代码和脱敏示例配置,不包含真实录取数据。部署前请阅读 [SECURITY.md](SECURITY.md),并始终从可信事件上下文覆盖 `requester_id`。

本项目采用 [MIT License](LICENSE)。