feat(tools): 支持向多个接收者发送卡片消息

重构 send_card_message 为 send_notion_card,支持接收者 ID 列表
更新 .gitignore 以忽略更多临时文件
升级项目版本至 0.1.17
删除未使用的 card.txt 模板文件
This commit is contained in:
2026-04-09 11:25:34 +08:00
parent e4909f159d
commit 49bdf45bfa
6 changed files with 76 additions and 223 deletions

6
.gitignore vendored
View File

@@ -1 +1,5 @@
__pycache__/ __pycache__/
*.txt
.DS*
test*

View File

@@ -1,199 +0,0 @@
{
"schema": "2.0",
"config": {
"update_multi": true,
"style": {
"text_size": {
"normal_v2": {
"default": "normal",
"pc": "normal",
"mobile": "heading"
}
}
}
},
"body": {
"direction": "vertical",
"elements": [
{
"tag": "form",
"elements": [
{
"tag": "markdown",
"content": "**<font color='blue-600'>确认单号:</font>** <font color='grey'>${order_number}</font>",
"text_align": "left",
"text_size": "normal"
},
{
"tag": "markdown",
"content": "**<font color='blue-600'>用户:</font>** <person id=${user_id} show_name=true show_avatar=true style='normal'></person>",
"text_align": "left",
"text_size": "normal"
},
{
"tag": "markdown",
"content": "**<font color='blue-600'>发生时间:</font>** <font color='grey'>${change_time}</font>",
"text_align": "left",
"text_size": "normal"
},
{
"tag": "markdown",
"content": "**<font color='blue-600'>\\*请准确选择关于您的变动项:</font>**",
"text_align": "left",
"text_size": "normal",
"margin": "0px 0px 8px 0px"
},
{
"tag": "multi_select_static",
"placeholder": {
"tag": "plain_text",
"content": "请选择资产变动项"
},
"options": "${asset_list}",
"type": "default",
"width": "fill",
"required": false,
"name": "input_assets",
"margin": "0px 0px 16px 0px",
"element_id": "cIiptD7Z4hCtAeR5Rb0b"
},
{
"tag": "hr",
"margin": "0px 0px 0px 0px"
},
{
"tag": "markdown",
"content": "**<font color='blue-600'>其他说明:</font>**",
"text_align": "left",
"text_size": "normal_v2",
"margin": "0px 0px 0px 0px"
},
{
"tag": "input",
"placeholder": {
"tag": "plain_text",
"content": "请输入"
},
"default_value": "",
"width": "fill",
"name": "input_remark",
"margin": "0px 0px 0px 0px"
},
{
"tag": "column_set",
"flex_mode": "flow",
"horizontal_spacing": "8px",
"horizontal_align": "right",
"columns": [
{
"tag": "column",
"width": "auto",
"elements": [
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "确认"
},
"type": "primary_filled",
"width": "default",
"size": "medium",
"behaviors": [
{
"type": "callback",
"value": {
"action": "card.action.trigger"
}
}
],
"form_action_type": "submit",
"name": "confirm_button",
"margin": "4px 0px 4px 0px"
}
],
"vertical_spacing": "8px",
"horizontal_align": "left",
"vertical_align": "top"
},
{
"tag": "column",
"width": "auto",
"elements": [
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "反馈问题"
},
"type": "default",
"width": "default",
"confirm": {
"title": {
"tag": "plain_text",
"content": "反馈误报并废除该确认单吗?"
},
"text": {
"tag": "plain_text",
"content": "${remark}"
}
},
"behaviors": [
{
"type": "callback",
"value": {
"action": "card.action.trigger"
}
}
],
"form_action_type": "submit",
"name": "feedback_button",
"margin": "4px 0px 4px 0px"
}
],
"vertical_spacing": "8px",
"horizontal_align": "left",
"vertical_align": "top"
}
]
}
],
"direction": "vertical",
"horizontal_align": "left",
"vertical_align": "top",
"padding": "12px 12px 12px 12px",
"margin": "0px 0px 0px 0px",
"name": "asset_confirmation_form"
},
{
"tag": "hr",
"margin": "0px 0px 0px 0px"
}
]
},
"header": {
"title": {
"tag": "plain_text",
"content": "资产变动单"
},
"subtitle": {
"tag": "plain_text",
"content": ""
},
"text_tag_list": [
{
"tag": "text_tag",
"text": {
"tag": "plain_text",
"content": "待确认"
},
"color": "orange"
}
],
"template": "blue",
"icon": {
"tag": "standard_icon",
"token": "googledrive_outlined"
},
"padding": "12px 8px 12px 8px"
}
}

View File

@@ -51,14 +51,17 @@ tools = [
} }
), ),
Tool( Tool(
name="send_card_message", name="send_notion_card",
description="发送消息卡片入参为receiver_id、person_id和image_key", description="发送消息卡片入参为receiver_ids、person_id和image_key",
inputSchema={ inputSchema={
"type": "object", "type": "object",
"properties": { "properties": {
"receiver_id": { "receiver_ids": {
"type": "string", "type": "array",
"description": "消息接收者ID" "description": "消息接收者ID列表",
"items": {
"type": "string"
}
}, },
"person_id": { "person_id": {
"type": "string", "type": "string",
@@ -69,7 +72,7 @@ tools = [
"description": "图片image_key" "description": "图片image_key"
} }
}, },
"required": ["receiver_id", "image_key"] "required": ["receiver_ids", "image_key"]
} }
), ),
Tool( Tool(
@@ -1768,8 +1771,25 @@ def send_feedback_card(
raise RuntimeError("failed to send message to any receiver") raise RuntimeError("failed to send message to any receiver")
return json.dumps(message_ids, ensure_ascii=False) return json.dumps(message_ids, ensure_ascii=False)
def send_notion_card(token: str, receiver_ids: List[str], person_id: str, image_key: str) -> str:
message_ids: List[str] = []
for receiver_id in receiver_ids:
normalized_receiver_id = str(receiver_id).strip()
if not normalized_receiver_id:
continue
try:
message_id = send_card_message(token, normalized_receiver_id, person_id, image_key)
if message_id:
message_ids.append(message_id)
except Exception:
pass
if not message_ids:
raise RuntimeError("failed to send message to any receiver")
return json.dumps(message_ids, ensure_ascii=False)
def send_card_message(token: str, receiver_id: str, person_id: str, image_key: str) -> str: def send_card_message(token: str, receiver_id: str, person_id: str, image_key: str) -> str:
utc_now = datetime.now(timezone.utc) utc_now = datetime.now(timezone.utc)
cst_now = utc_now + timedelta(hours=8) cst_now = utc_now + timedelta(hours=8)
@@ -1958,16 +1978,29 @@ async def handle_call_tool(name: str, arguments: Dict[str, object], token: str)
result = upload_image_by_url(token, image_source, image_type) result = upload_image_by_url(token, image_source, image_type)
else: else:
result = upload_image_by_file(token, image_source, image_type) result = upload_image_by_file(token, image_source, image_type)
elif name == "send_card_message": elif name == "send_notion_card":
image_key = str(arguments.get("image_key", "")).strip() image_key = str(arguments.get("image_key", "")).strip()
receiver_id = str(arguments.get("receiver_id", "")).strip() receiver_ids_value = arguments.get("receiver_ids")
raw_person_id = arguments.get("person_id") raw_person_id = arguments.get("person_id")
person_id = str(raw_person_id).strip() if raw_person_id is not None else "" person_id = str(raw_person_id).strip() if raw_person_id is not None else ""
if not receiver_id: if not receiver_ids_value:
raise ValueError("missing receiver_id") raise ValueError("missing receiver_ids")
if isinstance(receiver_ids_value, str):
normalized = receiver_ids_value.strip()
receiver_ids = [normalized] if normalized else []
elif isinstance(receiver_ids_value, list):
receiver_ids = [
str(item).strip()
for item in receiver_ids_value
if str(item).strip()
]
else:
raise ValueError("receiver_ids must be string or list of string")
if not receiver_ids:
raise ValueError("missing receiver_ids")
if not image_key: if not image_key:
raise ValueError("missing image_key") raise ValueError("missing image_key")
result = send_card_message(token, receiver_id, person_id, image_key) result = send_notion_card(token, receiver_ids, person_id, image_key)
elif name == "send_confirmation_card": elif name == "send_confirmation_card":
user_id = str(arguments.get("user_id", "")).strip() user_id = str(arguments.get("user_id", "")).strip()
order_number = str(arguments.get("order_number", "")).strip() order_number = str(arguments.get("order_number", "")).strip()

View File

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

View File

@@ -35,7 +35,7 @@ def main() -> None:
mcp_module.types = types_module mcp_module.types = types_module
sys.modules["mcp"] = mcp_module sys.modules["mcp"] = mcp_module
sys.modules["mcp.types"] = types_module sys.modules["mcp.types"] = types_module
from lzwcai_lark_mcp.tools import send_card_message, send_stranger_card from lzwcai_lark_mcp.tools import send_notion_card, send_stranger_card
app_id = os.getenv("app_id", "") app_id = os.getenv("app_id", "")
app_secret = os.getenv("app_secret", "") app_secret = os.getenv("app_secret", "")
auth_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" auth_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
@@ -53,18 +53,16 @@ def main() -> None:
if not token: if not token:
raise RuntimeError(f"lark auth response missing token: {data}") raise RuntimeError(f"lark auth response missing token: {data}")
user_id = "gegg1d78" user_id = "gegg1d78"
receiver_id = os.getenv("receiver_id", user_id) receiver_ids = ["843ga2gb", "gegg1d78"]
person_id = os.getenv("person_id", "") person_id = "gegg1d78"
image_key = os.getenv("image_key", "").strip() image_key = "img_v3_0210i_94bdf5de-5c89-49f0-a793-c504c7377c7g"
if not image_key: card_message_ids = send_notion_card(
raise RuntimeError("missing image_key")
card_message_id = send_card_message(
token, token,
receiver_id, receiver_ids,
person_id, person_id,
image_key image_key
) )
print(card_message_id) print(card_message_ids)
result = send_stranger_card( result = send_stranger_card(
token, token,
user_id, user_id,

17
mcp.json Normal file
View File

@@ -0,0 +1,17 @@
{
"mcpServers": {
"lark-mcp": {
"command": "npx",
"args": [
"-y",
"@larksuiteoapi/lark-mcp",
"mcp",
"-a",
"cli_a8d0e0c140169013",
"-s",
"yEc0E8Aoo8Mo9NPPzphidez51xB71HXW"
]
}
}
}