Merge pull request 'feishuokr' (#2) from feishuokr into master

Reviewed-on: http://git.lzwcai.com:3000/tanjianbin/lzwcai-mcp/pulls/2
This commit is contained in:
卢永锵
2026-04-03 09:44:23 +00:00
2 changed files with 191 additions and 1 deletions

View File

@@ -189,6 +189,62 @@ tools = [
"required": ["user_id", "order_number", "asset_list", "face_cap", "user_ids"]
}
),
Tool(
name="get_user_okr_list",
description="根据用户user_id获取该用户的OKR列表返回OKR ID及目标(Objective)和关键结果(Key Result)详情",
inputSchema={
"type": "object",
"properties": {
"user_id": {
"type": "string",
"description": "用户的user_id"
},
"offset": {
"type": "string",
"description": "分页偏移默认0"
},
"limit": {
"type": "string",
"description": "每页数量范围0-10默认10"
},
"lang": {
"type": "string",
"description": "语言版本: zh_cn 或 en_us默认 zh_cn",
"enum": ["zh_cn", "en_us"]
},
"period_ids": {
"type": "array",
"description": "OKR周期ID列表最多10个不传则返回所有周期",
"items": {
"type": "string"
}
}
},
"required": ["user_id"]
}
),
Tool(
name="batch_get_okr",
description="根据OKR ID批量获取OKR详情返回目标(Objective)及其关键结果(Key Result)详情",
inputSchema={
"type": "object",
"properties": {
"okr_ids": {
"type": "array",
"description": "OKR ID列表最多10个",
"items": {
"type": "string"
}
},
"lang": {
"type": "string",
"description": "语言版本: zh_cn 或 en_us默认 zh_cn",
"enum": ["zh_cn", "en_us"]
}
},
"required": ["okr_ids"]
}
),
Tool(
name="send_feedback_card",
description="发送资产变动反馈单入参为receiver_ids、user_id、order_number、change_time、asset_list和remark",
@@ -417,6 +473,107 @@ def _build_asset_options(inputs: List[str], outputs: List[str]) -> tuple[list[Di
return options, "".join(labels)
def get_user_okr_list(
token: str,
user_id: str,
offset: str = "0",
limit: str = "10",
lang: str = "zh_cn",
period_ids: List[str] | None = None,
) -> str:
url = f"https://open.feishu.cn/open-apis/okr/v1/users/{user_id}/okrs"
headers = {"Authorization": f"Bearer {token}"}
params: Dict[str, object] = {
"user_id_type": "user_id",
"offset": offset,
"limit": limit,
"lang": lang,
}
if period_ids:
params["period_ids"] = period_ids
response = requests.get(url, headers=headers, params=params, timeout=15)
response.raise_for_status()
payload = response.json()
if payload.get("code") not in (0, None):
raise RuntimeError(f"获取用户OKR列表失败: {payload}")
data = payload.get("data", {})
total = data.get("total", 0)
okr_list = data.get("okr_list", [])
return json.dumps({"total": total, "okr_list": okr_list}, ensure_ascii=False)
def batch_get_okr(
token: str,
okr_ids: List[str],
lang: str = "zh_cn",
) -> str:
url = "https://open.feishu.cn/open-apis/okr/v1/okrs/batch_get"
headers = {"Authorization": f"Bearer {token}"}
params: Dict[str, object] = {
"okr_ids": okr_ids,
"user_id_type": "user_id",
"lang": lang,
}
response = requests.get(url, headers=headers, params=params, timeout=15)
response.raise_for_status()
payload = response.json()
if payload.get("code") not in (0, None):
raise RuntimeError(f"批量获取OKR失败: {payload}")
data = payload.get("data", {})
okr_list = data.get("okr_list", [])
lines: List[str] = [f"共返回 {len(okr_list)} 条OKR记录"]
status_map = {"-1": "暂无", "0": "正常", "1": "风险", "2": "延期"}
confirm_map = {0: "初始状态", 1: "待提交", 2: "待确认", 3: "已拒绝", 4: "已确认"}
for idx, okr in enumerate(okr_list, 1):
okr_name = okr.get("name") or okr.get("id", "")
confirm = okr.get("confirm_status")
confirm_text = confirm_map.get(confirm, str(confirm)) if confirm is not None else ""
lines.append(f"\n{'='*40}")
lines.append(f"OKR #{idx}: {okr_name} (周期ID: {okr.get('period_id', '')})")
if confirm_text:
lines.append(f" 确认状态: {confirm_text}")
objective_list = okr.get("objective_list", [])
for oi, obj in enumerate(objective_list, 1):
content = obj.get("content", "")
score = obj.get("score", "")
weight = obj.get("weight", "")
progress_report = obj.get("progress_report", "")
progress = obj.get("progress_rate") or {}
percent = progress.get("percent", "")
status = status_map.get(str(progress.get("status", "")), "")
deadline = obj.get("deadline", "")
lines.append(f" O{oi}: {content}")
if weight:
lines.append(f" 权重: {weight}%")
if score:
lines.append(f" 评分: {score}")
if percent != "":
lines.append(f" 进度: {percent}%{(' (' + status + ')') if status else ''}")
if deadline and deadline != "0":
lines.append(f" 截止: {datetime.fromtimestamp(int(deadline) / 1000, tz=timezone(timedelta(hours=8))).strftime('%Y-%m-%d')}")
if progress_report:
lines.append(f" 备注: {progress_report}")
kr_list = obj.get("kr_list", [])
for ki, kr in enumerate(kr_list, 1):
kr_content = kr.get("content", "")
kr_score = kr.get("score", "")
kr_weight = kr.get("kr_weight", "")
kr_deadline = kr.get("deadline", "")
kr_progress = kr.get("progress_rate") or {}
kr_percent = kr_progress.get("percent", "")
kr_status = status_map.get(str(kr_progress.get("status", "")), "")
lines.append(f" KR{ki}: {kr_content}")
if kr_weight:
lines.append(f" 权重: {kr_weight}%")
if kr_score:
lines.append(f" 评分: {kr_score}")
if kr_percent != "":
lines.append(f" 进度: {kr_percent}%{(' (' + kr_status + ')') if kr_status else ''}")
if kr_deadline and kr_deadline != "0":
lines.append(f" 截止: {datetime.fromtimestamp(int(kr_deadline) / 1000, tz=timezone(timedelta(hours=8))).strftime('%Y-%m-%d')}")
return "\n".join(lines)
def _build_select_options(items: List[str]) -> list[Dict[str, object]]:
options: list[Dict[str, object]] = []
for item in items:
@@ -1883,6 +2040,39 @@ async def handle_call_tool(name: str, arguments: Dict[str, object], token: str)
user_ids,
remark
)
elif name == "get_user_okr_list":
user_id = str(arguments.get("user_id", "")).strip()
if not user_id:
raise ValueError("missing user_id")
offset = str(arguments.get("offset") or "0").strip()
limit = str(arguments.get("limit") or "10").strip()
lang = str(arguments.get("lang") or "zh_cn").strip()
period_ids = arguments.get("period_ids")
if period_ids is not None and not isinstance(period_ids, list):
period_ids = None
result = get_user_okr_list(
token,
user_id,
offset=offset,
limit=limit,
lang=lang,
period_ids=period_ids,
)
elif name == "batch_get_okr":
okr_ids = arguments.get("okr_ids")
if not okr_ids or not isinstance(okr_ids, list):
raise ValueError("missing or invalid okr_ids, must be a list of OKR ID strings")
if len(okr_ids) > 10:
raise ValueError("okr_ids supports at most 10 items")
okr_ids = [str(oid).strip() for oid in okr_ids if str(oid).strip()]
if not okr_ids:
raise ValueError("okr_ids is empty after filtering")
lang = str(arguments.get("lang") or "zh_cn").strip()
result = batch_get_okr(
token,
okr_ids,
lang=lang,
)
elif name == "send_feedback_card":
user_id = str(arguments.get("user_id", "")).strip()
order_number = str(arguments.get("order_number", "")).strip()

View File

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "lzwcai-lark-mcp"
version = "0.1.15"
version = "0.1.16"
description = "Lark MCP server"
requires-python = ">=3.10"
dependencies = [