feishu-user-plugin 1.3.10 → 1.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +27 -0
- package/.mcpb/manifest.json +91 -0
- package/CHANGELOG.md +18 -0
- package/PRIVACY.md +105 -0
- package/README.md +20 -0
- package/package.json +4 -2
- package/scripts/build-mcpb.js +119 -0
- package/scripts/check-mcp-registry-version.js +43 -0
- package/scripts/check-mcpb-version.js +33 -0
- package/scripts/check-version.js +5 -0
- package/scripts/sync-team-skills.sh +72 -57
- package/skills/feishu-user-plugin/SKILL.md +1 -1
- package/skills/feishu-user-plugin/references/CLAUDE.md +1 -0
- package/src/auth/credentials.js +49 -0
- package/src/auth/lark-desktop.js +135 -0
- package/src/server.js +42 -0
- package/src/setup.js +44 -0
- package/src/test-lark-desktop.js +300 -0
- package/scripts/generate-og-image.js +0 -39
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feishu-user-plugin",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.11",
|
|
4
4
|
"description": "All-in-one Feishu plugin for Claude Code — send messages as yourself, read chats (auto-expanded merge_forward), manage docs / bitable / wiki (full CRUD) / drive / OKR (with progress writes) / calendar (read+write) / Tasks v2 / multi-profile auto-switch / real-time WS events. 82 tools + 9 prompts, 3 auth layers.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "EthanQC"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "feishu-user-plugin",
|
|
3
|
+
"displayName": "Feishu MCP for Claude Code & Codex",
|
|
4
|
+
"description": "All-in-one Feishu MCP server for Claude Code & Codex — 84 tools across 3 auth layers (cookie / app / OAuth). Send as you, read groups, manage docs / bitable / wiki / drive / calendar / tasks / OKR.",
|
|
5
|
+
"version": "1.3.11",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "EthanQC"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://ethanqc.github.io/feishu-user-plugin/",
|
|
10
|
+
"repository": "https://github.com/EthanQC/feishu-user-plugin",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"category": "Communication",
|
|
13
|
+
"keywords": ["feishu", "lark", "mcp", "claude-code", "codex"],
|
|
14
|
+
"mcpServers": {
|
|
15
|
+
"feishu-user-plugin": {
|
|
16
|
+
"command": "npx",
|
|
17
|
+
"args": ["-y", "feishu-user-plugin"],
|
|
18
|
+
"env": {
|
|
19
|
+
"LARK_COOKIE": "${LARK_COOKIE}",
|
|
20
|
+
"LARK_APP_ID": "${LARK_APP_ID}",
|
|
21
|
+
"LARK_APP_SECRET": "${LARK_APP_SECRET}",
|
|
22
|
+
"LARK_USER_ACCESS_TOKEN": "${LARK_USER_ACCESS_TOKEN}",
|
|
23
|
+
"LARK_USER_REFRESH_TOKEN": "${LARK_USER_REFRESH_TOKEN}"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": "0.3",
|
|
3
|
+
"name": "feishu-user-plugin",
|
|
4
|
+
"display_name": "Feishu MCP for Claude Code & Codex",
|
|
5
|
+
"version": "1.3.11",
|
|
6
|
+
"description": "All-in-one Feishu MCP server for Claude Code & Codex — 84 tools across 3 auth layers (cookie / app / OAuth). Send as you, read groups, manage docs / bitable / wiki / drive / calendar / tasks / OKR.",
|
|
7
|
+
"long_description": "feishu-user-plugin is a local stdio MCP server that bridges Feishu / Lark and any MCP client (Claude Code, Codex, Cursor, Windsurf, OpenClaw, Claude Desktop). It exposes 84 tools across three auth layers: cookie + protobuf for sending messages as the real user (a capability not available through the official bot API), Feishu Open Platform app credentials for groups / docs / bitable / wiki / drive / calendar / tasks / OKR, and user OAuth (UAT) for P2P chat reading and user-owned resource creation.",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "EthanQC",
|
|
10
|
+
"url": "https://github.com/EthanQC"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://ethanqc.github.io/feishu-user-plugin/",
|
|
13
|
+
"documentation": "https://github.com/EthanQC/feishu-user-plugin/blob/main/README.md",
|
|
14
|
+
"support": "https://github.com/EthanQC/feishu-user-plugin/issues",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/EthanQC/feishu-user-plugin.git"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"keywords": [
|
|
21
|
+
"feishu",
|
|
22
|
+
"lark",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"codex",
|
|
25
|
+
"mcp",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"im",
|
|
28
|
+
"messaging",
|
|
29
|
+
"protobuf"
|
|
30
|
+
],
|
|
31
|
+
"privacy_policies": [
|
|
32
|
+
"https://github.com/EthanQC/feishu-user-plugin/blob/main/PRIVACY.md"
|
|
33
|
+
],
|
|
34
|
+
"server": {
|
|
35
|
+
"type": "node",
|
|
36
|
+
"entry_point": "src/index.js",
|
|
37
|
+
"mcp_config": {
|
|
38
|
+
"command": "node",
|
|
39
|
+
"args": ["${__dirname}/src/index.js"],
|
|
40
|
+
"env": {
|
|
41
|
+
"LARK_COOKIE": "${user_config.lark_cookie}",
|
|
42
|
+
"LARK_APP_ID": "${user_config.lark_app_id}",
|
|
43
|
+
"LARK_APP_SECRET": "${user_config.lark_app_secret}",
|
|
44
|
+
"LARK_USER_ACCESS_TOKEN": "${user_config.lark_user_access_token}",
|
|
45
|
+
"LARK_USER_REFRESH_TOKEN": "${user_config.lark_user_refresh_token}"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"user_config": {
|
|
50
|
+
"lark_cookie": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"title": "LARK_COOKIE",
|
|
53
|
+
"description": "Feishu web cookie (from feishu.cn DevTools → Network → request headers Cookie). Required for user-identity messaging tools.",
|
|
54
|
+
"required": false,
|
|
55
|
+
"sensitive": true
|
|
56
|
+
},
|
|
57
|
+
"lark_app_id": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"title": "LARK_APP_ID",
|
|
60
|
+
"description": "Feishu self-built app ID (cli_xxx). Required for Official API tools (group reads, docs, bitable, wiki, drive, calendar, tasks, OKR).",
|
|
61
|
+
"required": false,
|
|
62
|
+
"sensitive": true
|
|
63
|
+
},
|
|
64
|
+
"lark_app_secret": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"title": "LARK_APP_SECRET",
|
|
67
|
+
"description": "Feishu self-built app secret. Pairs with LARK_APP_ID.",
|
|
68
|
+
"required": false,
|
|
69
|
+
"sensitive": true
|
|
70
|
+
},
|
|
71
|
+
"lark_user_access_token": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"title": "LARK_USER_ACCESS_TOKEN",
|
|
74
|
+
"description": "User OAuth UAT (run `npx feishu-user-plugin oauth` to obtain). Required for P2P chat reading and user-owned resource creation.",
|
|
75
|
+
"required": false,
|
|
76
|
+
"sensitive": true
|
|
77
|
+
},
|
|
78
|
+
"lark_user_refresh_token": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"title": "LARK_USER_REFRESH_TOKEN",
|
|
81
|
+
"description": "User OAuth refresh token. Pairs with LARK_USER_ACCESS_TOKEN; auto-rotates the UAT on expiry.",
|
|
82
|
+
"required": false,
|
|
83
|
+
"sensitive": true
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"compatibility": {
|
|
87
|
+
"runtimes": {
|
|
88
|
+
"node": ">=18.0.0"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [1.3.11] - 2026-05-09
|
|
8
|
+
|
|
9
|
+
主线一项:Lark Desktop 多账号无感切换 — 用户在 Feishu Desktop 切账号,MCP 在 ~15 s 内自动跟进,无需任何 CLI 命令、无需 MCP 工具调用。配套三件 v1.4 prep(Privacy Policy / `.mcpb` manifest / `.cursor-plugin/plugin.json` / MCP Registry CI 自动 publish)已就位但未对外提交,等用户 dispatch。工具数 84 不变。
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Lark Desktop 多账号无感切换 (A)**:用户在 Feishu Desktop 切换账号 → MCP 自动跟进,不需要任何 CLI 命令、不需要 MCP 工具调用。`credentials.json::profiles[*].larkHash` 字段绑定 profile 与 `~/Library/Containers/com.bytedance.macos.feishu/Data/Library/Application Support/LarkShell/sdk_storage/<hash>/`;owner heartbeat (15 s) 监听各账号 `cookie_store.db` 的 mtime,最近活跃的 hash 与当前 active 不一致 + mtime 推进时调 `setActiveProfile`(5 s debounce)。`setup` 在 `fresh` / `update` 模式下自动绑定(单账号直接绑、多账号在交互模式下让用户选 / 非交互模式取最近活跃 + 在 stderr 列出其它)。新 CLI flag:`--bind-hash <hash>` 显式绑定,`--no-bind-hash` 跳过自动检测。未绑定但活跃的 hash 在 stderr 打一次性提示带 `setup --profile <name> --bind-hash <hash>` 命令。Lark 加密 `cookie_store.db` 全程不读不解密,cookie 仍由 `LARK_COOKIE` 按 profile 单独提供。macOS-only;Linux / Windows 默认无副作用。
|
|
13
|
+
- **`src/auth/lark-desktop.js` 模块**:`getSdkStorageDir()` / `listAccountHashes()` / `mostRecentHash()` / `detectSwitch()` 全部 fixture-testable,无新依赖(仅 `fs.statSync`)。13 个单元测试位于 `src/test-lark-desktop.js`,CI 友好(不依赖 Lark Desktop 安装)。
|
|
14
|
+
- **`.mcpb` 桌面扩展 manifest + Privacy Policy(v1.4 prep)**:`PRIVACY.md` 中英双语,按 Anthropic Connectors Directory 评审标准 6 维度组织(采集 / 处理 / 存储 / 第三方 / 留存 / 联系);`.mcpb/manifest.json` 走 `manifest_version=0.3` schema,含 `server.mcp_config` + `user_config` 块(5 个 `LARK_*` 全部声明 `sensitive=true`);`scripts/build-mcpb.js` 产出 `dist/feishu-user-plugin-<version>.mcpb`;CI gate `scripts/check-mcpb-version.js` 接进 `validate.yml`。**未提交**至 https://clau.de/desktop-extention-submission,等用户 dispatch(v1.4 计划)。
|
|
15
|
+
- **`.cursor-plugin/plugin.json`(v1.4 prep)**:按 Cursor plugins schema 编写(`mcpServers` 块镜像 README 的 Claude Code 配置);`scripts/check-version.js` 由 3 源扩到 4 源版本三角等价(`package.json` / `.claude-plugin/plugin.json` / `SKILL.md` / `.cursor-plugin/plugin.json`)。Schema 与 `docs/launch/submissions/cursor-marketplace.md` 校对发现 `author` 是 `{name, email}` 不是 `{name, url}`,已按官方 schema 修正。**未提交** Cursor Marketplace,等用户 dispatch。
|
|
16
|
+
- **MCP Registry CI 自动 publish(v1.4 prep)**:`.github/workflows/publish.yml` 增 mcp-publisher 步骤,`v*` tag 触发 → curl 安装 `mcp-publisher` → `login github-oidc`(runner OIDC token,无需 PAT)→ `publish mcp-registry.json`。`scripts/check-mcp-registry-version.js` 校验 `mcp-registry.json::version` + `packages[0].version` 与 `package.json::version` 一致;接进 `publish.yml`(pre-publish)+ `validate.yml`(PR-time)。从下个 release 起 Registry 同步零人工。
|
|
17
|
+
|
|
18
|
+
### Test scenarios
|
|
19
|
+
- 单 profile + 单 hash:`setup` 自动绑定,stderr 一行 `Bound profile "default" to Lark account hash <hex>`,无后续噪声
|
|
20
|
+
- 多 profile + 多 hash:在 Lark Desktop 切到 profile B 绑的账号 → 15 s 内 stderr 出 `Lark Desktop account changed; switching profile to "B"` → `credentials.json::active` 更新 → 下一次工具调用走 B 的凭证
|
|
21
|
+
- 未绑定但活跃 hash:在 Lark Desktop 切到一个新账号 → stderr 出一次性提示带 `setup --profile <name> --bind-hash <hash>` 命令;后续 heartbeat 不再重复
|
|
22
|
+
- 非 darwin:`getSdkStorageDir()` 返回 null,所有反应器代码 no-op;`setup --no-bind-hash` 显式跳过
|
|
23
|
+
- v1.4 prep 三件套不影响 v1.3.10 → v1.3.11 的运行时行为,仅扩 CI / metadata / packaging
|
|
24
|
+
|
|
7
25
|
## [1.3.10] - 2026-05-09
|
|
8
26
|
|
|
9
27
|
Growth track 一次性 ship + Official MCP Registry 上架。本版无新工具(84 不变),主体是发现入口、文档语气与发布元数据:仓库一句话描述与 npm description 同步、GitHub Pages 中文优先 SEO landing 上线、`README.md` 主版本切到中文、`docs/launch/` 13 文件 launch 草稿就位、Dockerfile 给 Glama listing introspection 用、自定义 OG image 替代 GitHub 默认渲染、CONTRIBUTING.md 双语重写。所有用户可见文档统一去除 reverse-engineering / 暴力探测 / 营销腔 / 合规免责段。
|
package/PRIVACY.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# 隐私政策 / Privacy Policy
|
|
2
|
+
|
|
3
|
+
`feishu-user-plugin` 是一个本地运行的 MCP 服务器。本文档说明插件如何处理用户提供的飞书 / Lark 凭证以及通过 MCP 工具调用流转的数据。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 中文
|
|
8
|
+
|
|
9
|
+
### 1. 收集的数据
|
|
10
|
+
|
|
11
|
+
插件本身不收集任何数据。运行需要用户主动配置以下凭证,全部来自用户自己的飞书 / Lark 账号:
|
|
12
|
+
|
|
13
|
+
- `LARK_COOKIE` —— 用户浏览器登录 feishu.cn 后从请求头复制的 cookie 串
|
|
14
|
+
- `LARK_APP_ID` + `LARK_APP_SECRET` —— 用户在飞书开放平台自建应用的 ID 与密钥
|
|
15
|
+
- `LARK_USER_ACCESS_TOKEN` + `LARK_USER_REFRESH_TOKEN` —— 用户通过 `npx feishu-user-plugin oauth` 在自己浏览器中授权后由插件本地保存的 OAuth 令牌
|
|
16
|
+
|
|
17
|
+
以上凭证保存在用户本地,不会发送给插件作者或除飞书自身以外的任何第三方。
|
|
18
|
+
|
|
19
|
+
### 2. 处理的数据
|
|
20
|
+
|
|
21
|
+
插件只处理用户通过 MCP 工具调用主动请求的数据:消息、文档、多维表格、知识库、云空间、日历、任务、OKR、联系人。插件是用户与飞书开放平台之间的薄代理,不在数据通过时做额外的留存、备份、上传或分析。
|
|
22
|
+
|
|
23
|
+
### 3. 数据存储位置
|
|
24
|
+
|
|
25
|
+
- 凭证文件:`~/.feishu-user-plugin/credentials.json`,文件权限 0600(仅当前用户可读写),由用户的操作系统强制访问控制
|
|
26
|
+
- 实时事件日志(启用时):`~/.feishu-user-plugin/events.jsonl`,append-only,10 MB 软上限 / 20 MB 硬上限自动轮转
|
|
27
|
+
- 不上报遥测,不发送埋点,不联网调用统计接口,不与插件作者维护的任何后台通信
|
|
28
|
+
|
|
29
|
+
唯一的数据驻留点是用户本机。
|
|
30
|
+
|
|
31
|
+
### 4. 第三方共享
|
|
32
|
+
|
|
33
|
+
插件运行时与两类外部方通信:
|
|
34
|
+
|
|
35
|
+
- **飞书开放平台 API**(`open.feishu.cn` / `feishu.cn`)—— 用户自己的飞书租户。所有读写都直接打到这里,等价于用户自己用飞书客户端的操作
|
|
36
|
+
- **用户运行的 AI 客户端**(Claude Code / Codex / Cursor / Windsurf / OpenClaw / Claude Desktop 等)—— 这是 MCP 协议的另一端,由用户自行选择安装
|
|
37
|
+
|
|
38
|
+
插件不引入任何额外的第三方依赖(无 CDN、无分析服务、无错误上报)。
|
|
39
|
+
|
|
40
|
+
### 5. 数据保留
|
|
41
|
+
|
|
42
|
+
完全由用户控制。插件不主动删除、归档或复制用户数据。要彻底移除:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
rm -rf ~/.feishu-user-plugin
|
|
46
|
+
npm uninstall -g feishu-user-plugin
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
撤销飞书侧的 OAuth 授权可在飞书开放平台的应用管理页操作。
|
|
50
|
+
|
|
51
|
+
### 6. 联系方式
|
|
52
|
+
|
|
53
|
+
- 一般问题:[GitHub Issues](https://github.com/EthanQC/feishu-user-plugin/issues)
|
|
54
|
+
- 安全披露:在 GitHub Issue 标题前加 `[security]` 前缀
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## English
|
|
59
|
+
|
|
60
|
+
### 1. Data Collected
|
|
61
|
+
|
|
62
|
+
The plugin itself collects no data. Operation requires the user to provide the following credentials, all from the user's own Feishu / Lark account:
|
|
63
|
+
|
|
64
|
+
- `LARK_COOKIE` — cookie string the user copies from their own browser session on feishu.cn
|
|
65
|
+
- `LARK_APP_ID` + `LARK_APP_SECRET` — credentials of a self-built app the user registers on the Feishu Open Platform
|
|
66
|
+
- `LARK_USER_ACCESS_TOKEN` + `LARK_USER_REFRESH_TOKEN` — OAuth tokens issued after the user grants consent via `npx feishu-user-plugin oauth` and saved locally by the plugin
|
|
67
|
+
|
|
68
|
+
These credentials remain on the user's machine and are not transmitted to the plugin author or any third party other than Feishu itself.
|
|
69
|
+
|
|
70
|
+
### 2. Data Processed
|
|
71
|
+
|
|
72
|
+
The plugin only processes data the user explicitly requests through MCP tool calls: messages, documents, bitable, wiki, drive, calendar, tasks, OKR, contacts. The plugin is a thin proxy between the user and the Feishu Open Platform; it does not retain, archive, replicate, upload, or analyse data in transit.
|
|
73
|
+
|
|
74
|
+
### 3. Where Data Is Stored
|
|
75
|
+
|
|
76
|
+
- Credential file: `~/.feishu-user-plugin/credentials.json`, mode 0600 (readable / writable only by the file owner), enforced by OS-level access control
|
|
77
|
+
- Realtime event log (when enabled): `~/.feishu-user-plugin/events.jsonl`, append-only, 10 MB soft / 20 MB hard rotation cap
|
|
78
|
+
- No telemetry, no analytics, no phone-home, no communication with any backend maintained by the plugin author
|
|
79
|
+
|
|
80
|
+
The user's machine is the only retention point.
|
|
81
|
+
|
|
82
|
+
### 4. Third-Party Sharing
|
|
83
|
+
|
|
84
|
+
At runtime the plugin communicates with two external parties:
|
|
85
|
+
|
|
86
|
+
- **Feishu Open Platform API** (`open.feishu.cn` / `feishu.cn`) — the user's own Feishu tenant. All reads and writes go directly there, equivalent to actions the user could take in the official Feishu client
|
|
87
|
+
- **The AI client the user runs** (Claude Code / Codex / Cursor / Windsurf / OpenClaw / Claude Desktop, etc.) — the other end of the MCP protocol, chosen and installed by the user
|
|
88
|
+
|
|
89
|
+
The plugin introduces no additional third party (no CDN, no analytics service, no error-reporting endpoint).
|
|
90
|
+
|
|
91
|
+
### 5. Data Retention
|
|
92
|
+
|
|
93
|
+
Entirely user-controlled. The plugin does not delete, archive, or replicate user data on its own. To remove everything:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
rm -rf ~/.feishu-user-plugin
|
|
97
|
+
npm uninstall -g feishu-user-plugin
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
OAuth authorization on the Feishu side can be revoked from the application management page of the Feishu Open Platform.
|
|
101
|
+
|
|
102
|
+
### 6. Contact
|
|
103
|
+
|
|
104
|
+
- General issues: [GitHub Issues](https://github.com/EthanQC/feishu-user-plugin/issues)
|
|
105
|
+
- Security disclosures: prefix the issue title with `[security]`
|
package/README.md
CHANGED
|
@@ -357,6 +357,26 @@ Issues / PR 欢迎。提交前先看 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
|
357
357
|
|
|
358
358
|
飞书改协议导致功能挂掉 —— 开 issue 带错误日志即可。
|
|
359
359
|
|
|
360
|
+
## 隐私 / Privacy
|
|
361
|
+
|
|
362
|
+
本地运行的 MCP 服务器,凭证留在用户本机,不上报遥测,不与插件作者后台通信。完整文本见 [PRIVACY.md](PRIVACY.md)。
|
|
363
|
+
|
|
364
|
+
- **收集**:插件本身不收集任何数据;`LARK_COOKIE` / `LARK_APP_ID` / `LARK_APP_SECRET` / `LARK_USER_ACCESS_TOKEN` / `LARK_USER_REFRESH_TOKEN` 全部由用户主动配置,来源于用户自己的飞书 / Lark 账号
|
|
365
|
+
- **处理**:仅处理用户通过 MCP 工具调用主动请求的消息 / 文档 / 多维表格 / 知识库 / 云空间 / 日历 / 任务 / OKR / 联系人,不留存、不分析
|
|
366
|
+
- **存储**:`~/.feishu-user-plugin/credentials.json`(mode 0600);可选事件日志 `~/.feishu-user-plugin/events.jsonl`(10 MB / 20 MB 自动轮转)
|
|
367
|
+
- **第三方**:仅与用户自己的飞书租户和用户运行的 AI 客户端通信,无 CDN / 分析 / 错误上报
|
|
368
|
+
- **保留**:完全用户控制;`rm -rf ~/.feishu-user-plugin && npm uninstall -g feishu-user-plugin` 即清空
|
|
369
|
+
- **联系**:[GitHub Issues](https://github.com/EthanQC/feishu-user-plugin/issues),安全问题在 issue 标题前加 `[security]`
|
|
370
|
+
|
|
371
|
+
A locally-run MCP server. Credentials stay on the user's machine; no telemetry, no phone-home. Full text at [PRIVACY.md](PRIVACY.md).
|
|
372
|
+
|
|
373
|
+
- **Collected**: nothing by the plugin itself; the five `LARK_*` envs are supplied by the user from their own Feishu / Lark account
|
|
374
|
+
- **Processed**: only the messages / docs / bitable / wiki / drive / calendar / tasks / OKR / contacts the user explicitly requests via MCP tool calls
|
|
375
|
+
- **Stored**: `~/.feishu-user-plugin/credentials.json` (mode 0600); optional event log at `~/.feishu-user-plugin/events.jsonl`
|
|
376
|
+
- **Third-party**: only the user's own Feishu tenant and the AI client the user runs (Claude Code / Codex / Cursor / etc.)
|
|
377
|
+
- **Retention**: entirely user-controlled; `rm -rf ~/.feishu-user-plugin && npm uninstall -g feishu-user-plugin` removes everything
|
|
378
|
+
- **Contact**: [GitHub Issues](https://github.com/EthanQC/feishu-user-plugin/issues); security disclosures with `[security]` prefix in the title
|
|
379
|
+
|
|
360
380
|
## License
|
|
361
381
|
|
|
362
382
|
[MIT](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feishu-user-plugin",
|
|
3
3
|
"mcpName": "io.github.EthanQC/feishu-user-plugin",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.11",
|
|
5
5
|
"description": "All-in-one Feishu MCP server for Claude Code & Codex — 84 tools across 3 auth layers (cookie / app / OAuth). Send as you, read groups, manage docs / bitable / wiki / drive / calendar / tasks / OKR.",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -45,12 +45,15 @@
|
|
|
45
45
|
"proto/",
|
|
46
46
|
"scripts/",
|
|
47
47
|
".claude-plugin/",
|
|
48
|
+
".cursor-plugin/",
|
|
49
|
+
".mcpb/",
|
|
48
50
|
"skills/",
|
|
49
51
|
".mcp.json.example",
|
|
50
52
|
".env.example",
|
|
51
53
|
"CHANGELOG.md",
|
|
52
54
|
"README.md",
|
|
53
55
|
"README.en.md",
|
|
56
|
+
"PRIVACY.md",
|
|
54
57
|
"LICENSE"
|
|
55
58
|
],
|
|
56
59
|
"engines": {
|
|
@@ -64,7 +67,6 @@
|
|
|
64
67
|
"protobufjs": "^7.5.6"
|
|
65
68
|
},
|
|
66
69
|
"devDependencies": {
|
|
67
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
68
70
|
"husky": "^9.1.7"
|
|
69
71
|
},
|
|
70
72
|
"overrides": {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// scripts/build-mcpb.js
|
|
5
|
+
//
|
|
6
|
+
// Packages the runtime files + .mcpb/manifest.json into
|
|
7
|
+
// dist/feishu-user-plugin-<version>.mcpb (a ZIP with manifest.json at the root).
|
|
8
|
+
//
|
|
9
|
+
// The .mcpb format is a plain ZIP archive consumed by Claude Desktop / Anthropic
|
|
10
|
+
// Connectors Directory. Required layout:
|
|
11
|
+
// manifest.json (at root, copied from .mcpb/manifest.json)
|
|
12
|
+
// src/... (server runtime)
|
|
13
|
+
// proto/... (protobuf descriptors)
|
|
14
|
+
// skills/... (MCP prompts source)
|
|
15
|
+
// .claude-plugin/... (plugin metadata)
|
|
16
|
+
// package.json (so `node src/index.js` resolves deps after `npm ci`)
|
|
17
|
+
// package-lock.json
|
|
18
|
+
// PRIVACY.md, README.md, LICENSE
|
|
19
|
+
//
|
|
20
|
+
// node_modules/ is NOT bundled; the connector host runs `npm ci --omit=dev`
|
|
21
|
+
// against the bundled package.json once installed (Anthropic convention).
|
|
22
|
+
//
|
|
23
|
+
// Re-runnable: overwrites dist/feishu-user-plugin-<version>.mcpb on each run.
|
|
24
|
+
//
|
|
25
|
+
// Usage:
|
|
26
|
+
// node scripts/build-mcpb.js
|
|
27
|
+
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
const { execFileSync } = require('child_process');
|
|
31
|
+
|
|
32
|
+
const ROOT = path.join(__dirname, '..');
|
|
33
|
+
const DIST = path.join(ROOT, 'dist');
|
|
34
|
+
const MANIFEST_SRC = path.join(ROOT, '.mcpb', 'manifest.json');
|
|
35
|
+
|
|
36
|
+
function fail(msg) {
|
|
37
|
+
console.error(`build-mcpb: ${msg}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(MANIFEST_SRC)) fail('.mcpb/manifest.json not found');
|
|
42
|
+
|
|
43
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
44
|
+
const manifest = JSON.parse(fs.readFileSync(MANIFEST_SRC, 'utf8'));
|
|
45
|
+
|
|
46
|
+
if (!manifest.version) fail('.mcpb/manifest.json missing `version`');
|
|
47
|
+
if (manifest.version !== pkg.version) {
|
|
48
|
+
fail(
|
|
49
|
+
`version mismatch — package.json=${pkg.version} but .mcpb/manifest.json=${manifest.version}. ` +
|
|
50
|
+
`Run: node scripts/check-mcpb-version.js`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const VERSION = pkg.version;
|
|
55
|
+
const OUT = path.join(DIST, `feishu-user-plugin-${VERSION}.mcpb`);
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync(DIST)) fs.mkdirSync(DIST, { recursive: true });
|
|
58
|
+
if (fs.existsSync(OUT)) fs.unlinkSync(OUT);
|
|
59
|
+
|
|
60
|
+
// Files & dirs to bundle. Order matters only for predictable zip output.
|
|
61
|
+
// Mirrors package.json::files plus PRIVACY.md and the bundled manifest.json.
|
|
62
|
+
const ENTRIES = [
|
|
63
|
+
'manifest.json', // synthesized at staging root from .mcpb/manifest.json
|
|
64
|
+
'src',
|
|
65
|
+
'proto',
|
|
66
|
+
'scripts',
|
|
67
|
+
'.claude-plugin',
|
|
68
|
+
'skills',
|
|
69
|
+
'package.json',
|
|
70
|
+
'package-lock.json',
|
|
71
|
+
'PRIVACY.md',
|
|
72
|
+
'README.md',
|
|
73
|
+
'LICENSE',
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// Stage in a tmp dir so the zip has manifest.json at the archive root rather
|
|
77
|
+
// than .mcpb/manifest.json. Using a tmp dir keeps the source tree clean.
|
|
78
|
+
const STAGE = fs.mkdtempSync(path.join(require('os').tmpdir(), 'mcpb-build-'));
|
|
79
|
+
try {
|
|
80
|
+
// Copy manifest.json to staging root
|
|
81
|
+
fs.copyFileSync(MANIFEST_SRC, path.join(STAGE, 'manifest.json'));
|
|
82
|
+
|
|
83
|
+
// Copy each remaining entry from repo root → staging root
|
|
84
|
+
for (const entry of ENTRIES.slice(1)) {
|
|
85
|
+
const src = path.join(ROOT, entry);
|
|
86
|
+
if (!fs.existsSync(src)) {
|
|
87
|
+
console.warn(`build-mcpb: skipping missing entry: ${entry}`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const dest = path.join(STAGE, entry);
|
|
91
|
+
copyRecursive(src, dest);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Create the ZIP via system `zip` (present on macOS + ubuntu-latest CI).
|
|
95
|
+
// -r recursive, -X strip extra OS attrs for reproducibility, -q quiet.
|
|
96
|
+
// We pass `.` so paths inside the zip are relative to the staging root.
|
|
97
|
+
execFileSync('zip', ['-rqX', OUT, '.'], { cwd: STAGE, stdio: 'inherit' });
|
|
98
|
+
} finally {
|
|
99
|
+
fs.rmSync(STAGE, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const stats = fs.statSync(OUT);
|
|
103
|
+
console.log(`OK: built ${path.relative(ROOT, OUT)} (${(stats.size / 1024).toFixed(1)} KB)`);
|
|
104
|
+
|
|
105
|
+
// --- helpers ------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
function copyRecursive(src, dest) {
|
|
108
|
+
const stat = fs.statSync(src);
|
|
109
|
+
if (stat.isDirectory()) {
|
|
110
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
111
|
+
for (const child of fs.readdirSync(src)) {
|
|
112
|
+
// Skip OS noise + already-built artifacts inside copied dirs
|
|
113
|
+
if (child === '.DS_Store' || child === 'node_modules' || child === 'dist') continue;
|
|
114
|
+
copyRecursive(path.join(src, child), path.join(dest, child));
|
|
115
|
+
}
|
|
116
|
+
} else if (stat.isFile()) {
|
|
117
|
+
fs.copyFileSync(src, dest);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// Verifies mcp-registry.json::version + packages[0].version == package.json::version.
|
|
4
|
+
// Wired into:
|
|
5
|
+
// - .github/workflows/publish.yml — pre-publish gate so CI never publishes to the
|
|
6
|
+
// official MCP Registry with a stale version string.
|
|
7
|
+
// - .github/workflows/validate.yml — PR-time gate so any version bump on
|
|
8
|
+
// package.json without a matching bump on mcp-registry.json fails before merge.
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const ROOT = path.join(__dirname, '..');
|
|
13
|
+
|
|
14
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
15
|
+
const pkgVersion = pkg.version;
|
|
16
|
+
|
|
17
|
+
const registryPath = path.join(ROOT, 'mcp-registry.json');
|
|
18
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
19
|
+
const registryVersion = registry.version;
|
|
20
|
+
|
|
21
|
+
if (!Array.isArray(registry.packages) || registry.packages.length === 0) {
|
|
22
|
+
console.error('ERROR: mcp-registry.json has no packages[] entries');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const pkgEntryVersion = registry.packages[0].version;
|
|
26
|
+
|
|
27
|
+
const sources = [
|
|
28
|
+
{ label: 'package.json', version: pkgVersion, path: 'package.json' },
|
|
29
|
+
{ label: 'mcp-registry.json::version', version: registryVersion, path: 'mcp-registry.json' },
|
|
30
|
+
{ label: 'mcp-registry.json::packages[0].version', version: pkgEntryVersion, path: 'mcp-registry.json' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const allEqual = sources.every((s) => s.version === sources[0].version);
|
|
34
|
+
|
|
35
|
+
if (!allEqual) {
|
|
36
|
+
console.error('ERROR: mcp-registry.json version mismatch with package.json!');
|
|
37
|
+
sources.forEach((s) => console.error(` ${s.label}: ${s.version}`));
|
|
38
|
+
console.error('Fix: bump mcp-registry.json::version AND mcp-registry.json::packages[0].version');
|
|
39
|
+
console.error(` to match package.json (${pkgVersion}).`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(`OK: mcp-registry.json version ${pkgVersion}`);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const ROOT = path.join(__dirname, '..');
|
|
7
|
+
|
|
8
|
+
// Source 1: package.json
|
|
9
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
10
|
+
const pkgVersion = pkg.version;
|
|
11
|
+
|
|
12
|
+
// Source 2: .mcpb/manifest.json
|
|
13
|
+
const manifestPath = path.join(ROOT, '.mcpb', 'manifest.json');
|
|
14
|
+
if (!fs.existsSync(manifestPath)) {
|
|
15
|
+
console.error('ERROR: .mcpb/manifest.json not found');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
19
|
+
const manifestVersion = manifest.version;
|
|
20
|
+
|
|
21
|
+
if (!manifestVersion) {
|
|
22
|
+
console.error('ERROR: .mcpb/manifest.json is missing the `version` field');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (pkgVersion !== manifestVersion) {
|
|
27
|
+
console.error('ERROR: .mcpb manifest version mismatch!');
|
|
28
|
+
console.error(` package.json: ${pkgVersion}`);
|
|
29
|
+
console.error(` .mcpb/manifest.json: ${manifestVersion}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(`OK: .mcpb manifest version ${pkgVersion}`);
|
package/scripts/check-version.js
CHANGED
|
@@ -22,10 +22,15 @@ if (!skillMatch) {
|
|
|
22
22
|
}
|
|
23
23
|
const skillVersion = skillMatch[1];
|
|
24
24
|
|
|
25
|
+
// Source 4: .cursor-plugin/plugin.json
|
|
26
|
+
const cursorPlugin = JSON.parse(fs.readFileSync(path.join(ROOT, '.cursor-plugin', 'plugin.json'), 'utf8'));
|
|
27
|
+
const cursorVersion = cursorPlugin.version;
|
|
28
|
+
|
|
25
29
|
const sources = [
|
|
26
30
|
{ label: 'package.json', version: pkgVersion, path: 'package.json' },
|
|
27
31
|
{ label: '.claude-plugin/plugin.json', version: pluginVersion, path: '.claude-plugin/plugin.json' },
|
|
28
32
|
{ label: 'skills/feishu-user-plugin/SKILL.md', version: skillVersion, path: 'skills/feishu-user-plugin/SKILL.md' },
|
|
33
|
+
{ label: '.cursor-plugin/plugin.json', version: cursorVersion, path: '.cursor-plugin/plugin.json' },
|
|
29
34
|
];
|
|
30
35
|
|
|
31
36
|
const versions = sources.map((s) => s.version);
|