makecindy / makecindy/cindy

[Bug] 飞书插件调用 VC 会议列表接口时缺少应用 Scope

Open
#137 6 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
2.7k
Forks
395
Avg merge
21h 48m
Merged PRs (30d)
776

Description

## 问题描述

在 Cindy 的「插件 → XD Feishu」中完成飞书账号授权后,调用 VC 会议列表接口失败:

```text
vc.v1.meeting_list.get
```

飞书接口返回:

```text
code: 99991679
msg: Unauthorized
```

目前 docx、im、contact、calendar、minutes、wiki、bitable、sheet、drive、task 等其它飞书能力可以正常使用,问题主要集中在 VC 及部分自动生成的只读直通接口。

根据用户从飞书开放平台获取的权限检查结果,这不是账号失效、测试参数错误或业务数据为空,而是当前 OAuth 应用缺少对应接口所需的应用 Scope。

## 复现步骤

1. 打开 Cindy。
2. 进入「插件 → XD Feishu」。
3. 点击「连接账号」,完成飞书 OAuth 授权。
4. 调用 `vc.v1.meeting_list.get`,或使用会议纪要相关能力触发 VC 会议列表查询。
5. 观察接口返回 `99991679 Unauthorized`。

## 实际结果

VC 会议列表接口调用失败,无法枚举指定时间范围内已发生的飞书会议。

## 预期结果

完成 XD Feishu 授权后,在当前用户对飞书会议具有访问权限的前提下,应能够调用 VC 会议列表接口并返回会议列表。

如果当前 OAuth 应用缺少 VC 权限,插件应明确提示缺少的具体 Scope,而不是只返回通用的 `Unauthorized`。

## 源码分析

### 1. VC 接口调用路径

当前插件调用 VC 会议列表接口的位置:

- `apps/desktop/resources/builtin-ghosts/xd-feishu/main.js:996-1032`
- `packages/lizi-mcps/src/feishu/mcp/server.ts:5481-5541`

请求接口为:

```text
GET /open-apis/vc/v1/meeting_list
```

请求参数包括:

```text
start_time
end_time
meeting_status=2
page_size=50
page_token
```

自动生成的工具定义位于:

- `packages/lizi-mcps/src/feishu/mcp/generated/zod/vc_v1.ts:234-272`

其定义为:

```text
name: vc.v1.meetingList.get
path: /open-apis/vc/v1/meeting_list
httpMethod: GET
accessTokens: tenant / user
```

当前 XD Feishu 插件的 OAuth 调用链实际使用 user access token。

### 2. 当前插件已经声明的 VC Scope

当前插件 Manifest:

```text
apps/desktop/resources/builtin-ghosts/xd-feishu/ghost.json:42-74
```

已声明以下 VC Scope:

```text
vc:meeting.meetingevent:read
vc:meeting.meetingid:read
vc:record:readonly
vc:reserve:readonly
```

OAuth 授权流程会将 Manifest 中声明的 Scope 拼入授权 URL:

```text
apps/desktop/src/main/cindy-brain/ghostOauthFlow.ts:548-555
```

插件设置页的「连接账号」默认申请 Manifest 中声明的完整 Scope:

```text
apps/desktop/resources/builtin-ghosts/xd-feishu/settings.js:129-157
```

### 3. 当前 Manifest 与实际接口权限存在缺口

当前 Manifest 没有声明以下权限:

```text
vc:room:readonly
vc:rooms.room.detailinfo:read
```

而用户从飞书开放平台获取的权限检查结果显示:

```text
vc.v1.meeting_list.get
→ vc:room:readonly 或 vc:rooms.room.detailinfo:read
```

因此,当前 `vc.v1.meeting_list.get` 返回:

```text
99991679 Unauthorized
```

与缺少 VC 应用 Scope 的现象一致。

插件源码也将 `99991679` 识别为权限 / Scope 错误:

```text
apps/desktop/resources/builtin-ghosts/xd-feishu/main.js:60-70
```

该错误与普通 OAuth token 过期不同。当前代码将 `99991668`、`99991672` 或 HTTP 401 作为 token 失效处理,因此本问题不像是普通登录态过期。

## 用户提供的飞书平台权限检查结果

用户从飞书开放平台获得的缺失权限列表如下:

| 类型 | 接口 | 所需 / 缺失 Scope |
|---|---|---|
| 视频会议 | `vc.v1.meeting_list.get` | `vc:room:readonly` 或 `vc:rooms.room.detailinfo:read` |
| 视频会议 | `vc.v1.export.get` | `vc:export` 或 `vc:meeting:export` |
| 群公告 | `docx.v1.chatAnnouncement.get` | `im:chat.announcement:read` |
| 群公告 | `docx.v1.chatAnnouncementBlock.list` | `im:chat.announcement:read` |
| Bitable 高级角色 | `base.v2.appRole.list` | `base:role:read` |
| Drive 文档版本 | `drive.v1.fileVersion.list` | `drive:drive:version` 或 `drive:drive:version:readonly` |
| Drive 浏览记录 | `drive.v1.fileViewRecord.list` | `drive:file:view_record:readonly`,以及 `contact:user.base:readonly` |
| Drive 点赞 | `drive.v2.fileLike.list` | `drive:file:like:readonly` |
| Contact 职务 | `contact.v3.jobTitle.list/get` | `contact:job_title:readonly` 等相关权限 |
| Contact 工作城市 | `contact.v3.workCity.list/get` | `contact:work_city:readonly` 等相关权限 |
| Calendar Exchange | `calendar.v4.exchangeBinding.get` | `calendar:exchange.bindings:read` 等相关权限 |

这些接口都属于当前 XD Feishu 的 OpenAPI 工具暴露范围。

当前插件实际包含:

- 12 个功能分类;
- 44 个精品操作;
- 123 个自动生成的只读直通操作;
- 生成工具统一位于 `apps/desktop/resources/builtin-ghosts/xd-feishu/main.js:4260-4384`;
- 默认工具注册逻辑位于 `packages/lizi-mcps/src/feishu/mcp/genTools.ts:188-249`。

因此,当前问题不应只检查 VC 权限,还应同步确认上述其它直通接口的权限配置。

## 飞书应用管理员权限检查清单

请飞书应用管理员进入飞书开放平台中的 XD Feishu 对应应用,对照以下清单检查:

- 应用后台是否已经开通;
- 是否需要管理员审批;
- 是否支持 user access token;
- 用户重新授权后实际返回的 granted scope 是否包含对应权限;
- 权限是否属于历史权限,是否已经无法继续申请;
- 如果权限已经废弃,飞书官方推荐的替代权限是什么。

### 一、当前 Manifest 已声明的 OAuth Scope

#### 基础授权

- [ ] `offline_access`

#### 云文档、知识库和云空间

- [ ] `docx:document`
- [ ] `bitable:app`
- [ ] `wiki:wiki`
- [ ] `drive:drive`

#### 即时消息

- [ ] `im:message`
- [ ] `im:message.send_as_user`
- [ ] `im:message:readonly`
- [ ] `im:chat:readonly`
- [ ] `im:resource`
- [ ] `search:message`

说明:

- `search:message` 用于跨会话搜索消息;
- `im:message:readonly` 不能自动替代 `search:message`;
- 单聊消息补充读取可能还需要额外的 p2p / 会话读取权限。

#### 日历

- [ ] `calendar:calendar`

#### 通讯录

- [ ] `contact:contact.base:readonly`
- [ ] `contact:user.email:readonly`
- [ ] `contact:user.department:readonly`
- [ ] `contact:user.department_path:readonly`
- [ ] `contact:department.base:readonly`
- [ ] `contact:user:search`

#### 妙记

- [ ] `minutes:minutes:readonly`
- [ ] `minutes:minutes.artifacts:read`
- [ ] `minutes:minutes.search:read`

#### 视频会议

- [ ] `vc:meeting.meetingevent:read`
- [ ] `vc:meeting.meetingid:read`
- [ ] `vc:record:readonly`
- [ ] `vc:reserve:readonly`

#### 任务

- [ ] `task:task:read`
- [ ] `task:tasklist:read`
- [ ] `task:section:read`
- [ ] `task:custom_field:read`
- [ ] `task:comment:read`
- [ ] `task:attachment:read`

### 二、用户提供的额外缺失 Scope

#### 视频会议

- [ ] `vc:room:readonly`
- [ ] `vc:rooms.room.detailinfo:read`
- [ ] `vc:export`
- [ ] `vc:meeting:export`

说明:

- `vc:room:readonly` 或 `vc:rooms.room.detailinfo:read` 与 `vc.v1.meeting_list.get` 相关;
- `vc:export` 或 `vc:meeting:export` 与 VC 导出任务相关;
- 部分权限可能属于历史权限,当前无法继续申请;
- 如果无法补充历史权限,需要确认飞书官方推荐的替代权限或接口。

#### 群公告

- [ ] `im:chat.announcement:read`

涉及接口:

```text
docx.v1.chatAnnouncement.get
docx.v1.chatAnnouncementBlock.list
```

#### Bitable 高级角色

- [ ] `base:role:read`

涉及接口:

```text
base.v2.appRole.list
```

#### Drive

- [ ] `drive:drive:version`
- [ ] `drive:drive:version:readonly`
- [ ] `drive:file:view_record:readonly`
- [ ] `drive:file:like:readonly`

另外,Drive 浏览记录接口还需要确认:

- [ ] `contact:user.base:readonly`

涉及接口:

```text
drive.v1.fileVersion.list
drive.v1.fileViewRecord.list
drive.v2.fileLike.list
```

#### Contact

- [ ] `contact:user.base:readonly`
- [ ] `contact:job_title:readonly`
- [ ] `contact:work_city:readonly`

涉及接口:

```text
contact.v3.jobTitle.list
contact.v3.jobTitle.get
contact.v3.workCity.list
contact.v3.workCity.get
```

#### Calendar Exchange

- [ ] `calendar:exchange.bindings:read`

涉及接口:

```text
calendar.v4.exchangeBinding.get
```

### 三、条件性权限

如果接口使用:

```text
user_id_type=user_id
```

或需要返回用户 User ID 等敏感字段,请检查:

- [ ] `contact:user.employee_id:readonly`

该权限可能影响以下接口:

- `vc.v1.meeting_list.get`
- `vc.v1.participantList.get`
- `vc.v1.participantQualityList.get`
- `vc.v1.resourceReservationList.get`
- VC 导出相关接口
- 部分 Drive、Contact、Calendar 接口

如果接口始终使用 `open_id` 或 `union_id`,请确认是否可以不申请该权限。

### 四、VC 会议详情扩展权限

如果后续需要从会议详情接口读取智能纪要或逐字稿,请检查:

- [ ] `vc:meeting.artifact.note:read`
- [ ] `vc:meeting.artifact.verbatim:read`

### 五、当前插件实际需要关注的 VC 接口

| 插件工具 | API | 重点权限 |
|---|---|---|
| `vc.v1.meetingList.get` | `GET /open-apis/vc/v1/meeting_list` | `vc:room:readonly` 或 `vc:rooms.room.detailinfo:read` |
| `vc.v1.meeting.get` | `GET /open-apis/vc/v1/meetings/:meeting_id` | `vc:meeting.meetingevent:read` 或 `vc:meeting:readonly` |
| `vc.v1.meetingRecording.get` | `GET /open-apis/vc/v1/meetings/:meeting_id/recording` | `vc:record:readonly` |
| `vc.v1.meeting.listByNo` | `GET /open-apis/vc/v1/meetings/list_by_no` | `vc:meeting.meetingid:read` 或 `vc:meeting:readonly` |
| `vc.v1.reserve.get` | `GET /open-apis/vc/v1/reserves/:reserve_id` | `vc:reserve:readonly` |
| `vc.v1.reserve.getActiveMeeting` | `GET /open-apis/vc/v1/reserves/:reserve_id/get_active_meeting` | `vc:reserve:readonly` |
| `vc.v1.participantList.get` | `GET /open-apis/vc/v1/participant_list` | `vc:room:readonly`,可能需要 `contact:user.employee_id:readonly` |
| `vc.v1.participantQualityList.get` | `GET /open-apis/vc/v1/participant_quality_list` | `vc:room:readonly`,可能需要 `contact:user.employee_id:readonly` |
| `vc.v1.resourceReservationList.get` | `GET /open-apis/vc/v1/resource_reservation_list` | `vc:room:readonly` |
| `vc.v1.export.get` | `GET /open-apis/vc/v1/exports/:task_id` | `vc:export` 或 `vc:meeting:export` |

## 建议修复方式

### 应用管理员侧

1. 在飞书开放平台进入 XD Feishu 对应应用。
2. 对照本 Issue 中的权限清单,检查当前应用已开通的 Scope。
3. 补齐当前插件实际使用但尚未开通的权限。
4. 对于已经废弃或无法继续申请的历史权限,确认飞书官方推荐的替代权限。
5. 发布新的应用版本。
6. 确认应用后台显示的权限已生效,并完成必要的管理员审批。

### 用户侧

1. 打开 Cindy。
2. 进入「插件 → XD Feishu」。
3. 点击「重新授权」或「连接账号」。
4. 完成新的飞书 OAuth 授权。
5. 确认新生成的 `user_access_token` 已包含新增 Scope。
6. 重新验证 VC 及其它受影响接口。

### 插件侧

建议后续改进:

1. 根据飞书返回的 `missing_scopes` 或权限错误信息,直接展示缺失的具体 Scope。
2. 对常用接口维护 endpoint → Scope 的映射表。
3. 在插件权限详情页展示当前功能对应的权限清单。
4. 对于 Manifest 声明了但后台未开通的 Scope,在连接账号前提供更明确的提示。
5. 对于已废弃或无法申请的历史 Scope,提供官方替代权限或降级方案。
6. 如果 VC 会议列表不可用,继续保留日历路径作为降级方案,并明确告知用户当前结果可能不完整。

## 需要管理员反馈的信息

请提供以下信息,便于确认问题位于应用后台、OAuth 授权还是用户资源权限:

1. 当前 XD Feishu OAuth 应用的 App ID。
2. 飞书开放平台中当前已开通的 VC 相关权限列表。
3. 是否存在管理员审批未完成的权限。
4. `vc:room:readonly` 与 `vc:rooms.room.detailinfo:read` 是否属于历史权限。
5. 如果以上权限无法继续申请,飞书官方推荐的替代权限是什么。
6. 用户重新授权后实际返回的 granted scope。
7. 当前账号是否能够在飞书客户端中查看目标会议及会议历史。
8. 当前账号是否是目标会议的组织者、参会人,或曾被会议呼叫。
9. 如果权限已经全部开通,调用接口是否仍然返回 `99991679 Unauthorized`。
10. 其它表格中列出的缺失 Scope 是否也会在对应接口调用时复现。

## 官方文档参考

### VC

- [查询会议明细](https://open.feishu.cn/document/server-docs/vc-v1/meeting-room-data/get.md)
- [查询参会人明细](https://open.feishu.cn/document/server-docs/vc-v1/meeting-room-data/get-2.md)
- [查询参会人会议质量数据](https://open.feishu.cn/document/server-docs/vc-v1/meeting-room-data/get-3.md)
- [查询会议室预定数据](https://open.feishu.cn/document/server-docs/vc-v1/meeting-room-data/get-4.md)
- [获取会议详情](https://open.feishu.cn/document/server-docs/vc-v1/meeting/get.md)
- [获取与会议号关联的会议列表](https://open.feishu.cn/document/server-docs/vc-v1/meeting/list_by_no.md)
- [获取录制文件](https://open.feishu.cn/document/server-docs/vc-v1/meeting-recording/get.md)
- [获取预约](https://open.feishu.cn/document/server-docs/vc-v1/reserve/get.md)
- [获取活跃会议](https://open.feishu.cn/document/server-docs/vc-v1/reserve/get_active_meeting.md)
- [查询导出任务结果](https://open.feishu.cn/document/server-docs/vc-v1/export/get.md)
- [导出会议明细](https://open.feishu.cn/document/server-docs/vc-v1/export/meeting_list.md)
- [导出参会人明细](https://open.feishu.cn/document/server-docs/vc-v1/export/participant_list.md)
- [导出参会人会议质量数据](https://open.feishu.cn/document/server-docs/vc-v1/export/participant_quality_list.md)
- [导出会议室预定数据](https://open.feishu.cn/document/server-docs/vc-v1/export/resource_reservation_list.md)

## 备注

当前源码中虽然存在 Manifest Scope 列表,但没有维护完整的:

```text
API endpoint → 功能 → 所需 Scope → 已开通状态
```

对照表。

因此本 Issue 中的权限清单分为两部分:

1. 当前插件 Manifest 已声明的 Scope;
2. 用户从飞书开放平台实际检查出的额外缺失 Scope。

最终应以飞书开放平台当前可申请、可开通的权限名称,以及用户重新授权后实际获得的 granted scope 为准。

截图中列出的部分权限可能属于历史权限、替代权限或不同应用类型下的权限名称,不能仅通过修改本地 `ghost.json` 解决。必须同步完成:

1. 飞书开放平台应用权限配置;
2. 应用版本发布;
3. 用户重新连接账号;
4. 新 `user_access_token` 权限验证;
5. 受影响接口回归测试。

Contributor guide

Open the contributing guide

Research direction

Start with apps/desktop/resources/builtin-ghosts/xd-feishu/ghost.json, main.js, settings.js, ghostOauthFlow.ts, and the VC call in packages/lizi-mcps/src/feishu/mcp/server.ts. Compare declared scopes with the generated VC endpoint definitions and verify the administrator-provided granted scopes. Done means the required scope configuration is confirmed, users can reauthorize successfully, and the affected VC call no longer returns 99991679.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
api, authentication, backend-api-design
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
43/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.