From 894ee1dfbf29c8b37676b941092f38ef1addfa5c Mon Sep 17 00:00:00 2001 From: tanjianbin <632190820@qq.com> Date: Thu, 19 Mar 2026 10:32:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(lark=5Fmcp):=20=E6=9B=B4=E6=96=B0=E9=A3=9E?= =?UTF-8?q?=E4=B9=A6=E5=8D=A1=E7=89=87=E6=B6=88=E6=81=AF=E5=8F=91=E9=80=81?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E4=BB=A5=E6=94=AF=E6=8C=81=E5=8D=95=E6=8E=A5?= =?UTF-8?q?=E6=94=B6=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将send_card_message工具从接收ID列表改为单个接收者ID - 修复test.py中导入并调用send_card_message的测试逻辑 - 新增机器人充电、终止任务、通知播放功能 - 扩展迎宾和巡逻功能参数,提供默认值和更灵活的配置 - 更新项目版本号至0.1.13和0.1.9 --- lzwcai_lark_mcp/lzwcai_lark_mcp/tools.py | 37 +++------ lzwcai_lark_mcp/pyproject.toml | 2 +- lzwcai_lark_mcp/test.py | 14 +++- lzwcai_temi_mcp/lzwcai_temi_mcp/main.py | 80 ++++++++++++++++--- lzwcai_temi_mcp/lzwcai_temi_mcp/nav_server.py | 50 +++++++++--- lzwcai_temi_mcp/pyproject.toml | 2 +- 6 files changed, 135 insertions(+), 50 deletions(-) diff --git a/lzwcai_lark_mcp/lzwcai_lark_mcp/tools.py b/lzwcai_lark_mcp/lzwcai_lark_mcp/tools.py index 9d6b433..78a2b55 100644 --- a/lzwcai_lark_mcp/lzwcai_lark_mcp/tools.py +++ b/lzwcai_lark_mcp/lzwcai_lark_mcp/tools.py @@ -52,16 +52,13 @@ tools = [ ), Tool( name="send_card_message", - description="发送消息卡片,入参为receiver_ids、person_id和image_key", + description="发送消息卡片,入参为receiver_id、person_id和image_key", inputSchema={ "type": "object", "properties": { - "receiver_ids": { - "type": "array", - "description": "消息接收者ID列表", - "items": { - "type": "string" - } + "receiver_id": { + "type": "string", + "description": "消息接收者ID" }, "person_id": { "type": "string", @@ -72,7 +69,7 @@ tools = [ "description": "图片image_key" } }, - "required": ["image_key"] + "required": ["receiver_id", "image_key"] } ), Tool( @@ -1766,18 +1763,7 @@ def send_card_message(token: str, receiver_id: str, person_id: str, image_key: s return message_id -def send_card_messages(token: str, receiver_ids: List[str], person_id: str, image_key: str) -> str: - if not receiver_ids: - raise ValueError("missing receiver_ids") - message_ids = [] - for receiver_id in receiver_ids: - normalized_receiver_id = str(receiver_id).strip() - if not normalized_receiver_id: - continue - message_ids.append(send_card_message(token, normalized_receiver_id, person_id, image_key)) - if not message_ids: - raise ValueError("missing receiver_ids") - return json.dumps(message_ids, ensure_ascii=False) + async def handle_call_tool(name: str, arguments: Dict[str, object], token: str) -> List[TextContent]: @@ -1816,16 +1802,13 @@ async def handle_call_tool(name: str, arguments: Dict[str, object], token: str) result = upload_image_by_file(token, image_source, image_type) elif name == "send_card_message": image_key = str(arguments.get("image_key", "")).strip() - receiver_ids = arguments.get("receiver_ids") + receiver_id = str(arguments.get("receiver_id", "")).strip() person_id = str(arguments.get("person_id", "")).strip() + if not receiver_id: + raise ValueError("missing receiver_id") if not image_key: raise ValueError("missing image_key") - if not person_id: - raise ValueError("missing person_id") - if receiver_ids is not None: - if not isinstance(receiver_ids, list): - raise ValueError("receiver_ids must be a list") - result = send_card_messages(token, receiver_ids, person_id, image_key) + result = send_card_message(token, receiver_id, person_id, image_key) elif name == "send_confirmation_card": user_id = str(arguments.get("user_id", "")).strip() order_number = str(arguments.get("order_number", "")).strip() diff --git a/lzwcai_lark_mcp/pyproject.toml b/lzwcai_lark_mcp/pyproject.toml index 62d6b48..e9bbda5 100644 --- a/lzwcai_lark_mcp/pyproject.toml +++ b/lzwcai_lark_mcp/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lzwcai-lark-mcp" -version = "0.1.11" +version = "0.1.13" description = "Lark MCP server" requires-python = ">=3.10" dependencies = [ diff --git a/lzwcai_lark_mcp/test.py b/lzwcai_lark_mcp/test.py index 95acb7e..0fecc1c 100644 --- a/lzwcai_lark_mcp/test.py +++ b/lzwcai_lark_mcp/test.py @@ -35,7 +35,7 @@ def main() -> None: mcp_module.types = types_module sys.modules["mcp"] = mcp_module sys.modules["mcp.types"] = types_module - from lzwcai_lark_mcp.tools import send_stranger_card + from lzwcai_lark_mcp.tools import send_card_message, send_stranger_card app_id = os.getenv("app_id", "") app_secret = os.getenv("app_secret", "") auth_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" @@ -53,6 +53,18 @@ def main() -> None: if not token: raise RuntimeError(f"lark auth response missing token: {data}") user_id = "gegg1d78" + receiver_id = os.getenv("receiver_id", user_id) + person_id = os.getenv("person_id", "") + image_key = os.getenv("image_key", "").strip() + if not image_key: + raise RuntimeError("missing image_key") + card_message_id = send_card_message( + token, + receiver_id, + person_id, + image_key + ) + print(card_message_id) result = send_stranger_card( token, user_id, diff --git a/lzwcai_temi_mcp/lzwcai_temi_mcp/main.py b/lzwcai_temi_mcp/lzwcai_temi_mcp/main.py index 5428724..2a66f18 100644 --- a/lzwcai_temi_mcp/lzwcai_temi_mcp/main.py +++ b/lzwcai_temi_mcp/lzwcai_temi_mcp/main.py @@ -23,6 +23,24 @@ async def serve() -> None: async def list_tools() -> list[Tool]: """列出所有工具""" return [ + Tool( + name="recharge", + description="轮足机器人充电", + inputSchema={ + "type": "object", + "properties": {}, + "required": [] + } + ), + Tool( + name="terminate", + description="轮足机器人终止当前任务", + inputSchema={ + "type": "object", + "properties": {}, + "required": [] + } + ), Tool( name="goto", description="轮足机器人导航到指定地点为用户引路。触发关键词:带我去、导航、引路、带路、怎么走、在哪里。", @@ -61,16 +79,41 @@ async def serve() -> None: "properties": { "location": { "type": "string", - "description": "引导接待客人到这个位置", + "description": "在这个位置接待贵宾", "minLength": 1 }, "name": { "type": "string", "description": "客人姓名", "minLength": 1 + }, + "destination": { + "type": "string", + "description": "接待贵宾到这个位置", + "minLength": 1 } }, - "required": ["location", "name"] + "required": ["location", "name", "destination"] + } + ), + Tool( + name="notification", + description="轮足机器人去指定地点播放通知:通知、去那里说、去某地播报、传达消息", + inputSchema={ + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "机器人需要前往的目标地点名称", + "minLength": 1 + }, + "text": { + "type": "string", + "description": "到达地点后需要播放的文本内容", + "minLength": 1 + } + }, + "required": ["location", "text"] } ), Tool( @@ -84,19 +127,23 @@ async def serve() -> None: ), Tool( name="patrol", - description="轮足机器人、助手、机器人去巡逻:巡逻、巡查、去检查一下、去看看、去巡视", + description="轮足机器人、助手、机器人去巡逻:巡逻、巡查、去检查一下、去看看、去巡视。支持按路线巡逻或随机巡逻。", inputSchema={ "type": "object", "properties": { "locations": { "type": "array", - "description": "机器人巡逻经过的地点列表", + "description": "机器人巡逻经过的地点列表。如果不提供,则默认进行随机巡逻。", "items": { "type": "string" } + }, + "flag": { + "type": "boolean", + "description": "是否随机巡逻。True为随机巡逻,False为按locations指定的路线巡逻。" } }, - "required": ["locations"] + "required": [] } ) ] @@ -106,7 +153,11 @@ async def serve() -> None: """处理工具调用""" try: result = "" - if name == "goto": + if name == "recharge": + result = await nav_server.recharge() + elif name == "terminate": + result = await nav_server.terminate() + elif name == "goto": if "location" not in arguments: raise ValueError("缺少必要参数: location") result = await nav_server.goto( @@ -122,16 +173,25 @@ async def serve() -> None: if "location" not in arguments: raise ValueError("缺少必要参数: location") result = await nav_server.reception( + location=arguments.get("location", "前台"), + name=arguments.get("name", "贵宾"), + destination=arguments.get("destination", "会议室") + ) + elif name == "notification": + if "location" not in arguments or "text" not in arguments: + raise ValueError("缺少必要参数: location or text") + result = await nav_server.notification( location=arguments["location"], - name=arguments.get("name", "贵宾") + text=arguments["text"] ) elif name == "repose": result = await nav_server.repose() elif name == "patrol": - if "locations" not in arguments: - raise ValueError("缺少必要参数: locations") + locations = arguments.get("locations", []) + flag = arguments.get("flag", True if not locations else False) result = await nav_server.patrol( - locations=arguments["locations"] + locations=locations, + flag=flag ) else: raise ValueError(f"未知工具: {name}") diff --git a/lzwcai_temi_mcp/lzwcai_temi_mcp/nav_server.py b/lzwcai_temi_mcp/lzwcai_temi_mcp/nav_server.py index 5c08c11..af94411 100644 --- a/lzwcai_temi_mcp/lzwcai_temi_mcp/nav_server.py +++ b/lzwcai_temi_mcp/lzwcai_temi_mcp/nav_server.py @@ -31,6 +31,25 @@ class NavServer: logger.error(f"Failed to publish command: {str(e)}", exc_info=True) return f"Failed to publish command: {str(e)}" + + async def recharge(self): + """轮足机器人充电""" + try: + params = {} + return await self.pub_cmd("temi-test", "recharge", params) + except Exception as e: + logger.error(f"Failed to call recharge mcp-tool: {str(e)} ", exc_info=True) + return f"Failed to call recharge mcp-tool: {str(e)}" + + async def terminate(self): + """轮足机器人终止当前任务""" + try: + params = {} + return await self.pub_cmd("temi-test", "terminate", params) + except Exception as e: + logger.error(f"Failed to call terminate mcp-tool: {str(e)} ", exc_info=True) + return f"Failed to call terminate mcp-tool: {str(e)}" + async def goto(self, location: str, flag: bool = False): """轮足导航到指定位置""" try: @@ -62,21 +81,34 @@ class NavServer: logger.error(f"Failed to call speak mcp-tool: {str(e)} ", exc_info=True) return f"Failed to call speak mcp-tool: {str(e)}" - async def reception(self, location: str, name: str = "贵宾"): + async def reception(self, location: str = "前台", name: str = "贵宾", destination: str = "会议室"): """轮足机器人移动到指定位置迎宾""" try: - if not location: - return "Location is not specified." - params = { "location": location, "text": f"您好,{name},我是接待机器人。", + "destination": destination } return await self.pub_cmd("temi-test", "reception", params) except Exception as e: logger.error(f"Failed to call reception mcp-tool: {str(e)} ", exc_info=True) return f"Failed to call reception mcp-tool: {str(e)}" + async def notification(self, location: str, text: str): + """轮足机器人移动到指定位置并播放通知文本""" + try: + if not location or not text: + return "Location or text is not specified." + + params = { + "location": location, + "text": text + } + return await self.pub_cmd("temi-test", "notification", params) + except Exception as e: + logger.error(f"Failed to call notification mcp-tool: {str(e)} ", exc_info=True) + return f"Failed to call notification mcp-tool: {str(e)}" + async def repose(self): """轮足机器人重新定位""" try: @@ -86,14 +118,12 @@ class NavServer: logger.error(f"Failed to call repose mcp-tool: {str(e)} ", exc_info=True) return f"Failed to call repose mcp-tool: {str(e)}" - async def patrol(self, locations: list): - """轮足机器人巡逻""" + async def patrol(self, locations: list = None, flag: bool = False): + """轮足机器人巡逻 why !!! """ try: - if not locations: - return "locations is not specified." params = { - "flag": False, - "locations": locations + "flag": True, + "locations": locations or [] } return await self.pub_cmd("temi-test", "patrol", params) except Exception as e: diff --git a/lzwcai_temi_mcp/pyproject.toml b/lzwcai_temi_mcp/pyproject.toml index 1fce1f7..d2dda44 100644 --- a/lzwcai_temi_mcp/pyproject.toml +++ b/lzwcai_temi_mcp/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lzwcai_temi_mcp" -version = "0.1.1" +version = "0.1.9" description = "MQTT-based navigation server for robot" requires-python = ">=3.10" dependencies = [