
Feishu Automation
- 367 installs
- 82 repo stars
- Updated August 2, 2026
- aaaaqwq/claude-code-skills
Automate Feishu/Lark bots, messages, approvals, and doc updates to connect team chat with internal tools and agent-triggered workflows.
About
Feishu-automation skill teaches Claude Code to integrate Lark/Feishu via bots, webhooks, and APIs for sending messages, syncing documents, routing approvals, and automating team workflows inside Chinese enterprise collaboration environments.
- Feishu/Lark API and bot automation
- Message, approval, and doc sync flows
- Agent-triggered workplace notifications
- Reduces manual team coordination steps
- Enterprise chat integration patterns
Feishu Automation by the numbers
- 367 all-time installs (skills.sh)
- Ranked #462 of 2,719 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aaaaqwq/claude-code-skills --skill feishu-automationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 367 |
|---|---|
| repo stars | ★ 82 |
| Last updated | August 2, 2026 |
| Repository | aaaaqwq/claude-code-skills ↗ |
What it does
Automate Feishu/Lark bots, messages, approvals, and doc updates to connect team chat with internal tools and agent-triggered workflows.
Files
飞书全通道自动化
使用 lark-mcp 工具实现飞书平台的全面自动化操作。
核心功能
1. 多维表格(Bitable)
- 创建多维表格和数据表
- 添加、修改、删除字段
- 增删改查记录数据
- 批量导入导出数据
- 数据筛选和排序
2. 消息发送
- 发送文本、富文本、卡片消息
- 群组消息和私聊消息
- 消息模板和交互式卡片
- 文件和图片发送
3. 文档管理
- 搜索云文档
- 创建新文档
- 编辑文档内容
- 文档权限管理
- 文档协作者管理
4. 群组管理
- 创建群组
- 添加/移除成员
- 获取群组列表
- 群组信息查询
5. 知识库(Wiki)
- 搜索知识库节点
- 获取节点详情
- 创建和管理知识库内容
6. 日历和任务
- 创建和查询日历事件
- 创建和管理任务
- 任务分配和跟踪
快速开始
检查 MCP 可用性
// 检查 lark-mcp 工具是否可用
// 可用工具前缀:mcp__lark-mcp_发送测试消息
// 发送文本消息到群组
await mcp__lark-mcp_sendMessage({
receive_id: "oc_xxxxxxxxx",
msg_type: "text",
content: JSON.stringify({
text: "Hello from Clawdbot!"
})
});工作流程
数据同步流程
1. 连接数据源 2. 转换数据格式 3. 创建/更新多维表格 4. 批量写入数据 5. 发送通知
消息推送流程
1. 触发事件(定时/事件驱动) 2. 构建消息内容 3. 获取接收者 ID 4. 发送消息 5. 记录日志
文档自动化流程
1. 获取文档模板 2. 填充内容 3. 创建新文档 4. 设置权限 5. 分享给团队
API 工具参考
多维表格相关
createBitable- 创建多维表格createTable- 创建数据表addRecord- 添加记录updateRecord- 更新记录deleteRecord- 删除记录searchRecords- 搜索记录getRecord- 获取记录详情
消息相关
sendMessage- 发送消息getMessages- 获取消息历史replyMessage- 回复消息
文档相关
searchDocs- 搜索文档createDoc- 创建文档getDoc- 获取文档内容updateDoc- 更新文档setDocPermission- 设置文档权限
📄 Markdown 导入云文档(推荐方式)
最佳实践:将本地 Markdown 文件直接导入为飞书云文档,格式完整保留。
# 1. 获取 access_token
TOKEN=$(curl -s -X POST 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal' \
-H 'Content-Type: application/json' \
-d '{"app_id":"YOUR_APP_ID","app_secret":"YOUR_APP_SECRET"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('tenant_access_token',''))")
# 2. 上传 md 文件到飞书云盘
FILE_TOKEN=$(curl -s -X POST 'https://open.feishu.cn/open-apis/drive/v1/files/upload_all' \
-H "Authorization: Bearer $TOKEN" \
-F "file_name=document.md" \
-F "parent_type=explorer" \
-F "parent_node=" \
-F "size=$(stat -c%s /path/to/document.md)" \
-F "file=@/path/to/document.md" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('file_token',''))")
# 3. 导入为飞书云文档
TICKET=$(curl -s -X POST 'https://open.feishu.cn/open-apis/drive/v1/import_tasks' \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"file_extension": "md",
"file_token": "'"$FILE_TOKEN"'",
"type": "docx",
"point": {"mount_type": 1, "mount_key": ""}
}' | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('ticket',''))")
# 4. 等待导入完成,获取文档链接
sleep 2
curl -s -X GET "https://open.feishu.cn/open-apis/drive/v1/import_tasks/$TICKET" \
-H "Authorization: Bearer $TOKEN" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('result',{}).get('url',''))"支持的导入格式:
md→ Markdowndocx→ Word 文档xlsx→ Excel 表格
注意事项:
- 导入是异步操作,需要轮询
import_tasks/{ticket}获取结果 mount_type: 1表示导入到"我的空间"- 导入后的文档 URL 格式:
https://xxx.feishu.cn/docx/{token}
📤 完整输出云文档流程(标准操作)
每次输出云文档时,必须完成以下步骤:
1. 生成本地 Markdown 文件 2. 上传到飞书云盘 → drive/v1/files/upload_all 3. 导入为云文档 → drive/v1/import_tasks 4. 设置权限为组织内可编辑 → drive/v1/permissions/{token}/public 5. 发送链接到目标群
# 完整流程脚本
TOKEN=$(curl -s -X POST 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal' \
-H 'Content-Type: application/json' \
-d '{"app_id":"APP_ID","app_secret":"APP_SECRET"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('tenant_access_token',''))")
# 1. 上传文件
FILE_TOKEN=$(curl -s -X POST 'https://open.feishu.cn/open-apis/drive/v1/files/upload_all' \
-H "Authorization: Bearer $TOKEN" \
-F "file_name=document.md" \
-F "parent_type=explorer" \
-F "parent_node=" \
-F "size=$(stat -c%s document.md)" \
-F "file=@document.md" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('file_token',''))")
# 2. 导入为云文档
TICKET=$(curl -s -X POST 'https://open.feishu.cn/open-apis/drive/v1/import_tasks' \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"file_extension":"md","file_token":"'"$FILE_TOKEN"'","type":"docx","point":{"mount_type":1,"mount_key":""}}' \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('ticket',''))")
sleep 2
# 3. 获取文档 token
DOC_RESULT=$(curl -s -X GET "https://open.feishu.cn/open-apis/drive/v1/import_tasks/$TICKET" \
-H "Authorization: Bearer $TOKEN")
DOC_TOKEN=$(echo "$DOC_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('result',{}).get('token',''))")
DOC_URL=$(echo "$DOC_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('result',{}).get('url',''))")
# 4. 设置权限:组织内可编辑
curl -s -X PATCH "https://open.feishu.cn/open-apis/drive/v1/permissions/$DOC_TOKEN/public?type=docx" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"external_access_entity":"open","security_entity":"anyone_can_view","comment_entity":"anyone_can_view","share_entity":"anyone","link_share_entity":"tenant_editable"}'
# 5. 发送到群
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"receive_id":"CHAT_ID","msg_type":"text","content":"{\"text\":\"📄 文档已上传:\\n\\n'"$DOC_URL"'\"}"}'权限设置说明:
link_share_entity: "tenant_editable"→ 组织内获得链接的人可编辑comment_entity: "anyone_can_view"→ 任何人可评论share_entity: "anyone"→ 任何人可分享
群组相关
createGroup- 创建群组addMember- 添加成员getGroupList- 获取群组列表getGroupInfo- 获取群组信息
实用场景
1. 自动化日报收集
- 每日定时创建表格记录
- 成员填写日报
- 自动汇总统计
- 发送到群组
2. 审批流程
- 创建审批表格
- 监听状态变更
- 自动通知审批人
- 记录审批历史
3. 任务管理
- 创建任务表格
- 分配任务给成员
- 发送任务提醒
- 跟踪完成状态
4. 客户管理
- 客户信息表格
- 跟进记录
- 自动提醒
- 数据可视化
5. 报表生成
- 从表格提取数据
- 生成统计报表
- 创建飞书文档
- 定期推送更新
最佳实践
认证配置
- 使用提供的 App ID 和 App Secret
- 遵守 API 调用频率限制
- 缓存 access_token
数据操作
- 批量操作使用分页
- 数据写入前验证格式
- 错误处理和重试
- 记录操作日志
消息发送
- 使用交互式卡片提升体验
- 合理控制发送频率
- 避免发送敏感信息
- 支持用户交互
权限管理
- 最小权限原则
- 定期审查权限
- 协作者生命周期管理
错误处理
常见错误
- 权限不足 - 检查应用权限配置
- 限流错误 - 实现重试和等待
- 无效 ID - 验证用户/群组 ID 格式
- 网络错误 - 实现重试机制
重试策略
- 指数退避算法
- 最大重试次数限制
- 记录失败请求
- 通知管理员
安全注意事项
- 保护 App Secret
- 不要在日志中记录敏感信息
- 使用环境变量管理凭证
- 定期轮换访问令牌
- 遵守数据隐私法规
示例代码
创建多维表格并添加数据
// 创建多维表格
const bitable = await mcp__lark-mcp_createBitable({
name: "项目管理",
folder_token: "folder_token"
});
// 创建数据表
const table = await mcp__lark-mcp_createTable({
app_token: bitable.app_token,
table: {
name: "任务列表",
fields: [
{ field_name: "任务名称", type: 1 },
{ field_name: "负责人", type: 13 },
{ field_name: "状态", type: 3 },
{ field_name: "截止日期", type: 5 }
]
}
});
// 添加记录
await mcp__lark-mcp_addRecord({
app_token: bitable.app_token,
table_id: table.table_id,
fields: {
"任务名称": "完成项目文档",
"负责人": "user_id",
"状态": "进行中",
"截止日期": Date.now()
}
});发送卡片消息
await mcp__lark-mcp_sendMessage({
receive_id: "chat_id",
msg_type: "interactive",
content: JSON.stringify({
config: {
wide_screen_mode: true
},
header: {
template: "turquoise",
title: {
content: "重要通知",
tag: "plain_text"
}
},
elements: [
{
tag: "div",
text: {
content: "**项目里程碑已完成**",
tag: "lark_md"
}
},
{
tag: "action",
actions: [
{
tag: "button",
text: {
content: "查看详情",
tag: "plain_text"
},
type: "primary",
url: "https://example.com"
}
]
}
]
})
});批量导入数据
const data = [
{ name: "张三", phone: "13800138000" },
{ name: "李四", phone: "13900139000" }
];
for (const item of data) {
await mcp__lark-mcp_addRecord({
app_token: "app_token",
table_id: "table_id",
fields: {
"姓名": item.name,
"电话": item.phone
}
});
// 避免限流
await new Promise(resolve => setTimeout(resolve, 100));
}配置验证
检查 MCP 服务器
# 检查 .claude.json 中的 lark-mcp 配置
cat ~/.claude.json | grep -A 15 "lark-mcp"测试连接
// 发送测试消息验证连接
await mcp__lark-mcp_sendMessage({
receive_id: "your_chat_id",
msg_type: "text",
content: JSON.stringify({
text: "🎉 飞书 MCP 连接成功!"
})
});进阶用法
Webhook 集成
- 监听飞书 Webhook 事件
- 自动触发工作流
- 实时数据同步
自动化定时任务
- 定时发送报告
- 自动数据备份
- 定期清理
跨平台集成
- 与 GitHub 集成(Issue 同步)
- 与邮件集成(通知推送)
- 与日历集成(日程管理)
故障排查
MCP 工具不可用
1. 重启 Claude Desktop 2. 检查网络连接 3. 验证凭证是否有效 4. 查看错误日志
API 调用失败
1. 检查应用权限配置 2. 验证用户/群组 ID 3. 查看限流状态 4. 检查数据格式
权限不足
1. 登录飞书开放平台 2. 检查应用权限范围 3. 重新授权 4. 等待权限生效
#!/usr/bin/env python3
"""
飞书多维表格快捷操作
"""
import sys
import json
from feishu_api import get_client
def list_tables(app_token: str):
"""列出数据表"""
client = get_client()
result = client.list_bitable_tables(app_token)
if result.get("code") == 0:
tables = result.get("data", {}).get("items", [])
print(f"找到 {len(tables)} 个数据表:\n")
for t in tables:
print(f" - {t.get('name')}")
print(f" ID: {t.get('table_id')}")
print()
else:
print(f"错误: {result}")
def list_records(app_token: str, table_id: str, limit: int = 10):
"""列出记录"""
client = get_client()
result = client.get_bitable_records(app_token, table_id, page_size=limit)
if result.get("code") == 0:
records = result.get("data", {}).get("items", [])
print(f"找到 {len(records)} 条记录:\n")
for r in records:
print(f" ID: {r.get('record_id')}")
print(f" 字段: {json.dumps(r.get('fields', {}), ensure_ascii=False, indent=4)}")
print()
else:
print(f"错误: {result}")
def add_record(app_token: str, table_id: str, fields_json: str):
"""添加记录"""
client = get_client()
fields = json.loads(fields_json)
result = client.add_bitable_record(app_token, table_id, fields)
if result.get("code") == 0:
record_id = result.get("data", {}).get("record", {}).get("record_id")
print(f"✅ 记录已添加: {record_id}")
else:
print(f"❌ 添加失败: {result}")
def update_record(app_token: str, table_id: str, record_id: str, fields_json: str):
"""更新记录"""
client = get_client()
fields = json.loads(fields_json)
result = client.update_bitable_record(app_token, table_id, record_id, fields)
if result.get("code") == 0:
print(f"✅ 记录已更新")
else:
print(f"❌ 更新失败: {result}")
def delete_record(app_token: str, table_id: str, record_id: str):
"""删除记录"""
client = get_client()
result = client.delete_bitable_record(app_token, table_id, record_id)
if result.get("code") == 0:
print(f"✅ 记录已删除")
else:
print(f"❌ 删除失败: {result}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("""
飞书多维表格操作
用法:
python bitable.py tables <app_token> # 列出数据表
python bitable.py records <app_token> <table_id> [limit] # 列出记录
python bitable.py add <app_token> <table_id> '<fields_json>' # 添加记录
python bitable.py update <app_token> <table_id> <record_id> '<fields_json>' # 更新记录
python bitable.py delete <app_token> <table_id> <record_id> # 删除记录
示例:
python bitable.py tables bascnXXXXXX
python bitable.py add bascnXXX tblXXX '{"名称": "测试", "状态": "进行中"}'
""")
sys.exit(1)
cmd = sys.argv[1]
if cmd == "tables" and len(sys.argv) >= 3:
list_tables(sys.argv[2])
elif cmd == "records" and len(sys.argv) >= 4:
limit = int(sys.argv[4]) if len(sys.argv) > 4 else 10
list_records(sys.argv[2], sys.argv[3], limit)
elif cmd == "add" and len(sys.argv) >= 5:
add_record(sys.argv[2], sys.argv[3], sys.argv[4])
elif cmd == "update" and len(sys.argv) >= 6:
update_record(sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5])
elif cmd == "delete" and len(sys.argv) >= 5:
delete_record(sys.argv[2], sys.argv[3], sys.argv[4])
else:
print(f"参数不足或未知命令: {cmd}")
sys.exit(1)
#!/usr/bin/env python3
"""
飞书卡片消息构建器
"""
import json
from typing import List, Dict, Any
class CardBuilder:
"""飞书卡片消息构建器"""
# 颜色模板
COLORS = {
"blue": "blue",
"wathet": "wathet", # 浅蓝
"turquoise": "turquoise", # 青色
"green": "green",
"yellow": "yellow",
"orange": "orange",
"red": "red",
"carmine": "carmine", # 深红
"violet": "violet",
"purple": "purple",
"indigo": "indigo",
"grey": "grey",
}
def __init__(self, title: str = None, color: str = "blue"):
self.config = {"wide_screen_mode": True}
self.header = None
self.elements = []
if title:
self.set_header(title, color)
def set_header(self, title: str, color: str = "blue") -> "CardBuilder":
"""设置标题"""
self.header = {
"template": self.COLORS.get(color, color),
"title": {"content": title, "tag": "plain_text"}
}
return self
def add_text(self, content: str, markdown: bool = True) -> "CardBuilder":
"""添加文本"""
self.elements.append({
"tag": "div",
"text": {
"content": content,
"tag": "lark_md" if markdown else "plain_text"
}
})
return self
def add_divider(self) -> "CardBuilder":
"""添加分割线"""
self.elements.append({"tag": "hr"})
return self
def add_note(self, content: str) -> "CardBuilder":
"""添加备注"""
self.elements.append({
"tag": "note",
"elements": [{"tag": "plain_text", "content": content}]
})
return self
def add_button(
self,
text: str,
url: str = None,
value: Dict = None,
type: str = "default"
) -> "CardBuilder":
"""添加按钮"""
button = {
"tag": "button",
"text": {"tag": "plain_text", "content": text},
"type": type # default, primary, danger
}
if url:
button["url"] = url
if value:
button["value"] = value
# 查找或创建 action 元素
action_elem = None
for elem in self.elements:
if elem.get("tag") == "action":
action_elem = elem
break
if action_elem:
action_elem["actions"].append(button)
else:
self.elements.append({
"tag": "action",
"actions": [button]
})
return self
def add_fields(self, fields: List[Dict[str, str]]) -> "CardBuilder":
"""添加字段列表"""
field_elements = []
for f in fields:
field_elements.append({
"is_short": f.get("short", True),
"text": {
"tag": "lark_md",
"content": f"**{f['label']}**\n{f['value']}"
}
})
self.elements.append({"tag": "div", "fields": field_elements})
return self
def add_image(self, image_key: str, alt: str = "") -> "CardBuilder":
"""添加图片"""
self.elements.append({
"tag": "img",
"img_key": image_key,
"alt": {"tag": "plain_text", "content": alt}
})
return self
def build(self) -> Dict:
"""构建卡片 JSON"""
card = {"config": self.config, "elements": self.elements}
if self.header:
card["header"] = self.header
return card
def to_json(self) -> str:
"""转换为 JSON 字符串"""
return json.dumps(self.build(), ensure_ascii=False)
# ==================== 预设模板 ====================
def notification_card(title: str, content: str, color: str = "blue") -> Dict:
"""通知卡片"""
return (CardBuilder(title, color)
.add_text(content)
.build())
def task_card(
title: str,
description: str,
assignee: str = None,
due_date: str = None,
status: str = "待处理"
) -> Dict:
"""任务卡片"""
builder = CardBuilder(f"📋 {title}", "turquoise")
builder.add_text(description)
fields = [{"label": "状态", "value": status}]
if assignee:
fields.append({"label": "负责人", "value": assignee})
if due_date:
fields.append({"label": "截止日期", "value": due_date})
builder.add_fields(fields)
return builder.build()
def alert_card(title: str, message: str, level: str = "warning") -> Dict:
"""告警卡片"""
colors = {"info": "blue", "warning": "orange", "error": "red", "success": "green"}
icons = {"info": "ℹ️", "warning": "⚠️", "error": "❌", "success": "✅"}
return (CardBuilder(f"{icons.get(level, '📢')} {title}", colors.get(level, "blue"))
.add_text(message)
.build())
def progress_card(title: str, items: List[Dict]) -> Dict:
"""进度卡片"""
builder = CardBuilder(f"📊 {title}", "green")
for item in items:
status_icon = "✅" if item.get("done") else "⏳"
builder.add_text(f"{status_icon} {item['name']}")
return builder.build()
if __name__ == "__main__":
# 示例
card = (CardBuilder("🎉 测试通知", "green")
.add_text("**飞书卡片消息测试**")
.add_text("这是一条来自小a的测试消息")
.add_divider()
.add_fields([
{"label": "发送时间", "value": "2026-02-03 15:30"},
{"label": "状态", "value": "✅ 成功"}
])
.add_button("查看详情", url="https://example.com", type="primary")
.add_note("由 OpenClaw 自动发送")
.build())
print(json.dumps(card, indent=2, ensure_ascii=False))
#!/usr/bin/env python3
"""
飞书多租户 API 工具包
支持:个人应用 + 汉兴企业应用
"""
import os
import json
import time
import requests
from typing import Optional, Dict, Any, List
# ==================== 租户配置 ====================
TENANTS = {
"personal": {
"name": "个人应用",
"app_id": "REDACTED_FEISHU_PERSONAL_APP_ID",
"app_secret": "REDACTED_FEISHU_PERSONAL_SECRET",
"default_chat": os.environ.get("FEISHU_PERSONAL_CHAT", ""), # 知识云文档
},
"hanxing": {
"name": "汉兴企业",
"app_id": "REDACTED_FEISHU_HANXING_APP_ID",
"app_secret": "REDACTED_FEISHU_HANXING_SECRET",
"default_chat": os.environ.get("FEISHU_HANXING_CHAT", ""), # 技术开发群
}
}
DEFAULT_TENANT = "hanxing" # 默认使用汉兴企业
class FeishuClient:
"""飞书 API 客户端"""
BASE_URL = "https://open.feishu.cn/open-apis"
def __init__(self, tenant: str = None):
tenant = tenant or DEFAULT_TENANT
if tenant not in TENANTS:
raise ValueError(f"未知租户: {tenant},可选: {list(TENANTS.keys())}")
config = TENANTS[tenant]
self.tenant_name = config["name"]
self.app_id = config["app_id"]
self.app_secret = config["app_secret"]
self.default_chat = config.get("default_chat")
self._token = None
self._token_expires = 0
@property
def token(self) -> str:
"""获取 tenant_access_token(自动刷新)"""
if self._token and time.time() < self._token_expires - 60:
return self._token
resp = requests.post(
f"{self.BASE_URL}/auth/v3/tenant_access_token/internal",
json={"app_id": self.app_id, "app_secret": self.app_secret}
)
data = resp.json()
if data.get("code") != 0:
raise Exception(f"获取 token 失败: {data}")
self._token = data["tenant_access_token"]
self._token_expires = time.time() + data["expire"]
return self._token
def _request(self, method: str, endpoint: str, **kwargs) -> Dict:
"""发送 API 请求"""
headers = kwargs.pop("headers", {})
headers["Authorization"] = f"Bearer {self.token}"
headers.setdefault("Content-Type", "application/json")
url = f"{self.BASE_URL}{endpoint}"
resp = requests.request(method, url, headers=headers, **kwargs)
return resp.json()
# ==================== 消息 API ====================
def send_message(
self,
receive_id: str,
content: str,
msg_type: str = "text",
receive_id_type: str = "open_id"
) -> Dict:
"""发送消息"""
return self._request(
"POST",
f"/im/v1/messages?receive_id_type={receive_id_type}",
json={
"receive_id": receive_id,
"msg_type": msg_type,
"content": content
}
)
def send_text(self, receive_id: str, text: str, receive_id_type: str = "open_id") -> Dict:
"""发送文本消息"""
return self.send_message(
receive_id,
json.dumps({"text": text}),
"text",
receive_id_type
)
def send_card(self, receive_id: str, card: Dict, receive_id_type: str = "open_id") -> Dict:
"""发送卡片消息"""
return self.send_message(
receive_id,
json.dumps(card),
"interactive",
receive_id_type
)
# ==================== 群组 API ====================
def list_chats(self, page_size: int = 50) -> Dict:
"""获取群组列表"""
return self._request("GET", f"/im/v1/chats?page_size={page_size}")
def get_chat(self, chat_id: str) -> Dict:
"""获取群组信息"""
return self._request("GET", f"/im/v1/chats/{chat_id}")
def create_chat(self, name: str, user_ids: List[str] = None) -> Dict:
"""创建群组"""
data = {"name": name}
if user_ids:
data["user_id_list"] = user_ids
return self._request("POST", "/im/v1/chats", json=data)
# ==================== 多维表格 API ====================
def list_bitable_tables(self, app_token: str) -> Dict:
"""获取多维表格的数据表列表"""
return self._request("GET", f"/bitable/v1/apps/{app_token}/tables")
def get_bitable_records(
self,
app_token: str,
table_id: str,
page_size: int = 20,
filter_str: str = None
) -> Dict:
"""获取多维表格记录"""
params = f"page_size={page_size}"
if filter_str:
params += f"&filter={filter_str}"
return self._request("GET", f"/bitable/v1/apps/{app_token}/tables/{table_id}/records?{params}")
def add_bitable_record(self, app_token: str, table_id: str, fields: Dict) -> Dict:
"""添加多维表格记录"""
return self._request(
"POST",
f"/bitable/v1/apps/{app_token}/tables/{table_id}/records",
json={"fields": fields}
)
def update_bitable_record(
self,
app_token: str,
table_id: str,
record_id: str,
fields: Dict
) -> Dict:
"""更新多维表格记录"""
return self._request(
"PUT",
f"/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}",
json={"fields": fields}
)
def delete_bitable_record(self, app_token: str, table_id: str, record_id: str) -> Dict:
"""删除多维表格记录"""
return self._request(
"DELETE",
f"/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}"
)
# ==================== 文档 API ====================
def search_docs(self, query: str, count: int = 20) -> Dict:
"""搜索文档"""
return self._request(
"POST",
"/suite/docs-api/search/object",
json={"search_key": query, "count": count}
)
# ==================== 用户 API ====================
def get_user(self, user_id: str, user_id_type: str = "open_id") -> Dict:
"""获取用户信息"""
return self._request(
"GET",
f"/contact/v3/users/{user_id}?user_id_type={user_id_type}"
)
# ==================== 便捷函数 ====================
_clients = {}
def get_client(tenant: str = None) -> FeishuClient:
"""获取客户端实例"""
tenant = tenant or DEFAULT_TENANT
if tenant not in _clients:
_clients[tenant] = FeishuClient(tenant)
return _clients[tenant]
def send_text(receive_id: str, text: str, receive_id_type: str = "open_id", tenant: str = None) -> Dict:
"""发送文本消息"""
return get_client(tenant).send_text(receive_id, text, receive_id_type)
def send_to_chat(chat_id: str, text: str, tenant: str = None) -> Dict:
"""发送消息到群组"""
return get_client(tenant).send_text(chat_id, text, "chat_id")
def list_chats(tenant: str = None) -> List[Dict]:
"""获取群组列表"""
result = get_client(tenant).list_chats()
return result.get("data", {}).get("items", [])
# ==================== CLI ====================
if __name__ == "__main__":
import sys
def print_help():
print("""
飞书多租户 API 工具
用法:
python feishu_api.py [--tenant <name>] <command> [args...]
租户:
personal - 个人应用 (REDACTED...)
hanxing - 汉兴企业 (REDACTED...) [默认]
命令:
test # 测试连接
chats # 列出群组
send <chat_id> <text> # 发送消息到群组
user <open_id> # 获取用户信息
示例:
python feishu_api.py test
python feishu_api.py --tenant personal chats
python feishu_api.py --tenant hanxing send oc_xxx "Hello"
""")
# 解析参数
args = sys.argv[1:]
tenant = None
if len(args) >= 2 and args[0] == "--tenant":
tenant = args[1]
args = args[2:]
if len(args) < 1:
print_help()
sys.exit(1)
cmd = args[0]
try:
client = get_client(tenant)
print(f"[{client.tenant_name}] ", end="")
if cmd == "test":
print(f"Token 获取成功: {client.token[:20]}...")
elif cmd == "chats":
result = client.list_chats()
if result.get("code") == 0:
chats = result.get("data", {}).get("items", [])
if not chats:
print("没有找到群组(机器人可能还未加入任何群)")
else:
print(f"找到 {len(chats)} 个群组:\n")
for chat in chats:
print(f" - {chat.get('name', 'N/A')}")
print(f" ID: {chat.get('chat_id')}")
print()
else:
print(f"错误: {result}")
elif cmd == "send" and len(args) >= 3:
chat_id = args[1]
text = " ".join(args[2:])
result = client.send_text(chat_id, text, "chat_id")
if result.get("code") == 0:
print(f"消息已发送")
else:
print(f"发送失败: {result}")
elif cmd == "send-default" and len(args) >= 2:
# 发送到默认群组
if not client.default_chat:
print(f"错误: 该租户未配置默认群组")
sys.exit(1)
text = " ".join(args[1:])
result = client.send_text(client.default_chat, text, "chat_id")
if result.get("code") == 0:
print(f"消息已发送到默认群组")
else:
print(f"发送失败: {result}")
elif cmd == "user" and len(args) >= 2:
user_id = args[1]
result = client.get_user(user_id)
print(json.dumps(result, indent=2, ensure_ascii=False))
else:
print(f"未知命令或参数不足: {cmd}")
print_help()
sys.exit(1)
except Exception as e:
print(f"错误: {e}")
sys.exit(1)
#!/usr/bin/env node
/**
* Feishu MCP Configuration Checker and Setup
* 检测飞书 MCP 配置状态,并在需要时自动配置到用户级 .claude.json
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
const CLAUDE_JSON_PATH = path.join(os.homedir(), '.claude.json');
const APP_ID_PLACEHOLDER = '<your_app_id>';
const APP_SECRET_PLACEHOLDER = '<your_app_secret>';
// 飞书 MCP 服务器配置模板
const LARK_MCP_CONFIG_TEMPLATE = {
command: 'npx',
args: [
'-y',
'@larksuiteoapi/lark-mcp',
'mcp',
'-a', APP_ID_PLACEHOLDER,
'-s', APP_SECRET_PLACEHOLDER,
'-t', 'preset.light,preset.default,preset.im.default,preset.base.default,preset.base.batch,preset.doc.default,preset.task.default,preset.calendar.default,docx.v1.documentBlock.patch,docx.v1.documentBlockChildren.create,docx.v1.documentBlockChildren.batchDelete'
]
};
// 必需的权限列表
const REQUIRED_PRESETS = [
'preset.light',
'preset.default',
'preset.im.default',
'preset.base.default',
'preset.base.batch',
'preset.doc.default',
'preset.task.default',
'preset.calendar.default'
];
const REQUIRED_EXTRA_PERMISSIONS = [
'docx.v1.documentBlock.patch',
'docx.v1.documentBlockChildren.create',
'docx.v1.documentBlockChildren.batchDelete'
];
/**
* 读取现有的 .claude.json 文件
*/
function readClaudeJson() {
try {
if (fs.existsSync(CLAUDE_JSON_PATH)) {
const content = fs.readFileSync(CLAUDE_JSON_PATH, 'utf8');
return JSON.parse(content);
}
} catch (error) {
console.error('读取 .claude.json 失败:', error.message);
}
return { mcpServers: {} };
}
/**
* 检查 lark-mcp 是否已配置
*/
function checkLarkMcpConfig(config) {
if (!config.mcpServers) {
return { configured: false, reason: 'mcpServers 配置不存在' };
}
const larkMcp = config.mcpServers['lark-mcp'];
if (!larkMcp) {
return { configured: false, reason: 'lark-mcp 服务器未配置' };
}
// 检查是否为数组
if (!Array.isArray(larkMcp.args)) {
return { configured: false, reason: 'lark-mcp 配置格式不正确' };
}
// 检查是否包含必需参数
const argsStr = larkMcp.args.join(' ');
// 检查 app_id 和 app_secret 是否已配置
if (argsStr.includes(APP_ID_PLACEHOLDER) || argsStr.includes(APP_SECRET_PLACEHOLDER)) {
return {
configured: 'incomplete',
reason: 'lark-mcp 已配置但缺少 App ID 或 App Secret'
};
}
// 检查必需的 preset 权限
const missingPresets = REQUIRED_PRESETS.filter(preset => !argsStr.includes(preset));
if (missingPresets.length > 0) {
return {
configured: 'incomplete',
reason: `缺少必需的 preset 权限: ${missingPresets.join(', ')}`
};
}
// 检查额外的权限
const missingExtraPermissions = REQUIRED_EXTRA_PERMISSIONS.filter(perm => !argsStr.includes(perm));
if (missingExtraPermissions.length > 0) {
return {
configured: 'incomplete',
reason: `缺少额外的权限: ${missingExtraPermissions.join(', ')}`
};
}
return { configured: true, reason: 'lark-mcp 已正确配置' };
}
/**
* 生成完整的 MCP 配置
*/
function generateMcpConfig(appId, appSecret) {
return {
'lark-mcp': {
command: 'npx',
args: [
'-y',
'@larksuiteoapi/lark-mcp',
'mcp',
'-a', appId,
'-s', appSecret,
'-t', REQUIRED_PRESETS.join(',') + ',' + REQUIRED_EXTRA_PERMISSIONS.join(',')
]
}
};
}
/**
* 更新 .claude.json 文件
*/
function updateClaudeJson(mcpConfig) {
const config = readClaudeJson();
if (!config.mcpServers) {
config.mcpServers = {};
}
// 合并新的 MCP 配置
config.mcpServers = { ...config.mcpServers, ...mcpConfig };
try {
fs.writeFileSync(
CLAUDE_JSON_PATH,
JSON.stringify(config, null, 2) + '\n',
'utf8'
);
return true;
} catch (error) {
console.error('写入 .claude.json 失败:', error.message);
return false;
}
}
/**
* 主函数 - 检查并配置飞书 MCP
*/
async function main() {
console.log('🔍 飞书 MCP 配置检测工具\n');
console.log('='.repeat(50));
// 1. 读取现有配置
console.log('\n📖 检查现有配置...');
const currentConfig = readClaudeJson();
// 2. 检查配置状态
console.log('\n✅ 检查 lark-mcp 配置状态...');
const checkResult = checkLarkMcpConfig(currentConfig);
console.log(`\n配置状态: ${checkResult.configured === true ? '已配置' : checkResult.configured === 'incomplete' ? '部分配置' : '未配置'}`);
console.log(`原因: ${checkResult.reason}`);
if (checkResult.configured === true) {
console.log('\n✨ 飞书 MCP 已经正确配置!');
console.log('您可以开始使用飞书相关功能。');
return;
}
// 3. 提示用户输入凭证
console.log('\n⚙️ 开始配置飞书 MCP...\n');
// 检查是否提供了命令行参数
const args = process.argv.slice(2);
let appId, appSecret;
if (args.length >= 2) {
// 从命令行参数获取
appId = args[0];
appSecret = args[1];
} else {
// 提示用户输入(模拟,实际使用时会从标准输入读取)
console.log('请提供飞书应用凭证:');
console.log(' - App ID: 您的飞书应用 ID');
console.log(' - App Secret: 您的飞书应用密钥');
// 在实际使用中,这里应该使用 readline 或其他方式获取输入
// 为了脚本化使用,建议通过命令行参数传递
console.log('\n💡 使用提示:');
console.log(' node feishu-mcp-setup.js <app_id> <app_secret>');
console.log('\n或者您可以手动配置 .claude.json 文件。\n');
return;
}
// 4. 生成配置
console.log('📝 生成飞书 MCP 配置...');
const mcpConfig = generateMcpConfig(appId, appSecret);
// 5. 更新配置文件
console.log('💾 更新配置文件...');
if (updateClaudeJson(mcpConfig)) {
console.log('\n✅ 配置成功!');
console.log(`\n📄 配置文件已更新: ${CLAUDE_JSON_PATH}`);
console.log('\n✨ 请重启 Claude Desktop 应用以加载新的 MCP 服务器配置。');
} else {
console.log('\n❌ 配置失败!');
process.exit(1);
}
}
// 导出函数供其他模块使用
module.exports = {
readClaudeJson,
checkLarkMcpConfig,
generateMcpConfig,
updateClaudeJson,
REQUIRED_PRESETS,
REQUIRED_EXTRA_PERMISSIONS,
LARK_MCP_CONFIG_TEMPLATE
};
// 如果直接运行此脚本
if (require.main === module) {
main().catch(console.error);
}
#!/bin/bash
# Feishu MCP Setup Script
# 用法: ./feishu-mcp-setup.sh <app_id> <app_secret>
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
NODE_SCRIPT="$SCRIPT_DIR/feishu-mcp-setup.js"
# 检查 Node.js 是否可用
if ! command -v node &> /dev/null; then
echo "❌ 错误: 需要安装 Node.js"
echo "请访问 https://nodejs.org/ 安装 Node.js"
exit 1
fi
# 检查是否提供了凭证
if [ $# -lt 2 ]; then
echo "🔧 飞书 MCP 配置工具"
echo "========================"
echo ""
echo "用法: $0 <app_id> <app_secret>"
echo ""
echo "参数:"
echo " app_id 飞书应用的 App ID"
echo " app_secret 飞书应用的 App Secret"
echo ""
echo "示例:"
echo " $0 cli_a1b2c3d4e5f6 app_secret_xxx"
echo ""
echo "💡 提示: 您也可以直接运行 Node.js 脚本进行交互式配置:"
echo " node $NODE_SCRIPT"
exit 1
fi
APP_ID="$1"
APP_SECRET="$2"
echo "🔧 配置飞书 MCP..."
echo "App ID: $APP_ID"
echo ""
# 运行 Node.js 配置脚本
node "$NODE_SCRIPT" "$APP_ID" "$APP_SECRET"
#!/bin/bash
# 飞书快捷发送脚本(多租户)
#
# 用法:
# ./feishu-send.sh "消息" # 发送到汉兴默认群
# ./feishu-send.sh <chat_id> "消息" # 发送到指定群
# ./feishu-send.sh --personal "消息" # 发送到个人默认群
# ./feishu-send.sh --personal <chat_id> "消息"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# 默认配置
TENANT=""
CHAT_ID=""
MESSAGE=""
# 解析参数
while [[ $# -gt 0 ]]; do
case $1 in
--personal|-p)
TENANT="personal"
shift
;;
--hanxing|-h)
TENANT="hanxing"
shift
;;
*)
if [[ -z "$MESSAGE" ]]; then
# 检查是否是 chat_id (以 oc_ 开头)
if [[ "$1" == oc_* ]]; then
CHAT_ID="$1"
else
MESSAGE="$1"
fi
else
MESSAGE="$MESSAGE $1"
fi
shift
;;
esac
done
if [[ -z "$MESSAGE" ]]; then
echo "用法: $0 [--personal|--hanxing] [chat_id] <消息>"
echo ""
echo "示例:"
echo " $0 \"Hello\" # 发送到汉兴技术开发群"
echo " $0 --personal \"Hello\" # 发送到个人知识云文档群"
echo " $0 oc_xxx \"Hello\" # 发送到指定群"
exit 1
fi
# 构建命令
CMD="python3 $SCRIPT_DIR/feishu_api.py"
if [[ -n "$TENANT" ]]; then
CMD="$CMD --tenant $TENANT"
fi
if [[ -n "$CHAT_ID" ]]; then
$CMD send "$CHAT_ID" "$MESSAGE"
else
# 使用默认群组
$CMD send-default "$MESSAGE"
fi
#!/bin/bash
# md2feishu.sh - 将 Markdown 文件导入飞书云文档
# 用法: ./md2feishu.sh /path/to/document.md [chat_id]
# 如果提供 chat_id,会自动发送文档链接到群
#
# 标准流程:
# 1. 上传到云盘
# 2. 导入为云文档
# 3. 设置权限(组织内可编辑)
# 4. 发送到群(可选)
set -e
MD_FILE="$1"
CHAT_ID="$2"
if [ -z "$MD_FILE" ]; then
echo "用法: $0 <markdown_file> [chat_id]"
exit 1
fi
if [ ! -f "$MD_FILE" ]; then
echo "错误: 文件不存在: $MD_FILE"
exit 1
fi
# 从 pass 获取凭证(汉兴企业)
APP_ID="REDACTED_FEISHU_HANXING_APP_ID"
APP_SECRET=$(pass show api/feishu-hanxing 2>/dev/null || echo "")
if [ -z "$APP_SECRET" ]; then
echo "错误: 无法获取飞书 App Secret,请检查 pass api/feishu-hanxing"
exit 1
fi
echo "📤 正在上传: $MD_FILE"
# 1. 获取 access_token
TOKEN=$(curl -s -X POST 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal' \
-H 'Content-Type: application/json' \
-d '{"app_id":"'"$APP_ID"'","app_secret":"'"$APP_SECRET"'"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('tenant_access_token',''))")
if [ -z "$TOKEN" ]; then
echo "错误: 获取 access_token 失败"
exit 1
fi
# 2. 上传文件到飞书云盘
FILE_NAME=$(basename "$MD_FILE")
FILE_SIZE=$(stat -c%s "$MD_FILE")
echo "📁 上传文件到云盘..."
UPLOAD_RESULT=$(curl -s -X POST 'https://open.feishu.cn/open-apis/drive/v1/files/upload_all' \
-H "Authorization: Bearer $TOKEN" \
-F "file_name=$FILE_NAME" \
-F "parent_type=explorer" \
-F "parent_node=" \
-F "size=$FILE_SIZE" \
-F "file=@$MD_FILE")
FILE_TOKEN=$(echo "$UPLOAD_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('file_token',''))")
if [ -z "$FILE_TOKEN" ]; then
echo "错误: 上传文件失败"
echo "$UPLOAD_RESULT"
exit 1
fi
echo "✅ 文件上传成功: $FILE_TOKEN"
# 3. 导入为飞书云文档
echo "📝 导入为云文档..."
IMPORT_RESULT=$(curl -s -X POST 'https://open.feishu.cn/open-apis/drive/v1/import_tasks' \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"file_extension": "md",
"file_token": "'"$FILE_TOKEN"'",
"type": "docx",
"point": {"mount_type": 1, "mount_key": ""}
}')
TICKET=$(echo "$IMPORT_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('ticket',''))")
if [ -z "$TICKET" ]; then
echo "错误: 创建导入任务失败"
echo "$IMPORT_RESULT"
exit 1
fi
echo "⏳ 等待导入完成..."
sleep 2
# 4. 获取导入结果
TASK_RESULT=$(curl -s -X GET "https://open.feishu.cn/open-apis/drive/v1/import_tasks/$TICKET" \
-H "Authorization: Bearer $TOKEN")
DOC_TOKEN=$(echo "$TASK_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('result',{}).get('token',''))")
DOC_URL=$(echo "$TASK_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('result',{}).get('url',''))")
JOB_STATUS=$(echo "$TASK_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('result',{}).get('job_status',1))")
if [ "$JOB_STATUS" != "0" ] || [ -z "$DOC_URL" ]; then
echo "错误: 导入失败"
echo "$TASK_RESULT"
exit 1
fi
echo "✅ 导入成功!"
# 5. 设置权限:组织内获得链接的人可编辑
echo "🔐 设置文档权限..."
curl -s -X PATCH "https://open.feishu.cn/open-apis/drive/v1/permissions/$DOC_TOKEN/public?type=docx" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_access_entity": "open",
"security_entity": "anyone_can_view",
"comment_entity": "anyone_can_view",
"share_entity": "anyone",
"link_share_entity": "tenant_editable"
}' > /dev/null
echo "✅ 权限已设置:组织内可编辑"
echo ""
echo "📄 文档链接: $DOC_URL"
# 6. 如果提供了 chat_id,发送到群
if [ -n "$CHAT_ID" ]; then
echo ""
echo "📤 发送到群组: $CHAT_ID"
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"receive_id": "'"$CHAT_ID"'",
"msg_type": "text",
"content": "{\"text\":\"📄 文档已上传(组织内可编辑):\\n\\n'"$DOC_URL"'\"}"
}' > /dev/null
echo "✅ 已发送到群组"
fi
echo ""
echo "🎉 完成!"