feat(tools): 支持向多个接收者发送卡片消息
重构 send_card_message 为 send_notion_card,支持接收者 ID 列表 更新 .gitignore 以忽略更多临时文件 升级项目版本至 0.1.17 删除未使用的 card.txt 模板文件
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,5 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
|
*.txt
|
||||||
|
.DS*
|
||||||
|
|
||||||
|
test*
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(
|
||||||
@@ -1770,6 +1773,23 @@ def send_feedback_card(
|
|||||||
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()
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user