From d7ee22ae67260f3441651a4e6a9efea3bfba0790 Mon Sep 17 00:00:00 2001 From: KhazixW2 <3196497671@qq.com> Date: Tue, 24 Mar 2026 12:57:51 +0800 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20=E9=87=8D=E5=91=BD=E5=90=8D=20p?= =?UTF-8?q?ipeline=20=E6=A8=A1=E5=9D=97=E4=B8=BA=20pipeline=5Ftools=20?= =?UTF-8?q?=E4=BB=A5=E6=98=8E=E7=A1=AE=E5=8A=9F=E8=83=BD=E5=AE=9A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 `pipeline.py` 重命名为 `pipeline_tools.py` 以更准确反映模块功能 - 更新主模块导入语句以使用新模块名 - 保持所有工具函数和功能不变,仅修改模块名称 --- maa_mcp/main.py | 2 +- maa_mcp/{pipeline.py => pipeline_tools.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename maa_mcp/{pipeline.py => pipeline_tools.py} (100%) diff --git a/maa_mcp/main.py b/maa_mcp/main.py index 396c4d7..5a983a2 100644 --- a/maa_mcp/main.py +++ b/maa_mcp/main.py @@ -8,4 +8,4 @@ from maa_mcp import vision # noqa: F401 from maa_mcp import control # noqa: F401 from maa_mcp import utils # noqa: F401 -from maa_mcp import pipeline # noqa: F401 +from maa_mcp import pipeline_tools # noqa: F401 diff --git a/maa_mcp/pipeline.py b/maa_mcp/pipeline_tools.py similarity index 100% rename from maa_mcp/pipeline.py rename to maa_mcp/pipeline_tools.py From 46944e733fb5816aa4045d33c1b26e672e9756cd Mon Sep 17 00:00:00 2001 From: KhazixW2 <3196497671@qq.com> Date: Tue, 24 Mar 2026 15:58:40 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(pipeline):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=9A=E8=B5=84=E6=BA=90=E8=B7=AF=E5=BE=84=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=8F=8A=E4=BC=98=E5=8C=96=20run=5Fpipeline=20=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - resource: 支持 add_resource_path() 添加额外资源路径,后加载的覆盖先加载的同名资源 - resource: 优化加载逻辑,避免重复加载默认路径 - pipeline_tools: run_pipeline 添加 resource_path 参数支持外部资源目录 - pipeline_tools: 优化返回格式,包含 status 文字、node_count、nodes 详情及识别结果 - pipeline_server: 添加 pipeline_tools 导入 解决 MaaGC 等外部 Pipeline 无法正确加载资源的问题。 --- maa_mcp/pipeline_server.py | 1 + maa_mcp/pipeline_tools.py | 89 ++++++++++++++++++++++++++++++++++++-- maa_mcp/resource.py | 65 +++++++++++++++++++++++++--- 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/maa_mcp/pipeline_server.py b/maa_mcp/pipeline_server.py index 6f9629c..5b01e71 100644 --- a/maa_mcp/pipeline_server.py +++ b/maa_mcp/pipeline_server.py @@ -24,6 +24,7 @@ import maa_mcp.control import maa_mcp.utils import maa_mcp.resource +import maa_mcp.pipeline_tools # 导入 Pipeline 子模块 from dataclasses import dataclass diff --git a/maa_mcp/pipeline_tools.py b/maa_mcp/pipeline_tools.py index 55f6b02..496785f 100644 --- a/maa_mcp/pipeline_tools.py +++ b/maa_mcp/pipeline_tools.py @@ -18,7 +18,7 @@ from maa_mcp.core import mcp from maa_mcp.paths import get_data_dir -from maa_mcp.resource import get_or_create_resource, get_or_create_tasker +from maa_mcp.resource import get_or_create_resource, get_or_create_tasker, add_resource_path # Pipeline 协议文档(精简版,包含 AI 生成 Pipeline 所需的关键信息) @@ -391,11 +391,52 @@ def save_pipeline( - controller_id: 控制器 ID,由 connect_adb_device() 或 connect_window() 返回 - pipeline_path: Pipeline JSON 文件路径 - entry: 入口节点名称(可选),不指定则使用 Pipeline 中的第一个节点 + - resource_path: 资源目录路径(可选),用于指定 Pipeline 所需的资源文件路径。 + 如果不指定,则使用 MaaMCP 默认的资源目录。 + 当 Pipeline 需要使用外部资源(如 MaaGC 的资源)时,需要指定此参数。 返回值: - - 成功:返回 TaskDetail 对象,包含 task_id、entry、status、nodes 等执行信息 + - 成功:返回 dict 对象,包含以下字段: + - task_id: 任务 ID + - entry: 入口节点名称 + - status: 执行状态字符串("succeeded" | "failed" | "running" | "pending" | "done") + - node_count: 执行的节点数量 + - nodes: 节点详情列表,每个节点包含: + - name: 节点名称 + - recognition: 识别结果(如果有),包含 all_results 列表 + - all_results: 识别到的目标列表,每项包含 box(坐标)和 score(置信度) - 失败:返回错误信息字符串 + 判断识别结果: + - 检查 nodes 是否包含 recognition.all_results: + - 有内容 = 识别成功,找到了目标 + - 无内容或 nodes 为空 = 识别失败,未找到目标 + - box 格式:[x, y, width, height] + - score 范围:0-1,越高越准确 + + 示例返回值(识别成功): + { + "task_id": 200000001, + "entry": "BackButton_500ms", + "status": "succeeded", + "node_count": 1, + "nodes": [{ + "name": "BackButton_500ms", + "recognition": { + "all_results": [{"box": [653, 7, 46, 40], "score": 0.999726}] + } + }] + } + + 示例返回值(识别失败): + { + "task_id": 200000002, + "entry": "BackButton_500ms", + "status": "failed", + "node_count": 1, + "nodes": [{"name": ""}] + } + 说明: 此函数会先加载 Pipeline 文件到 Resource,然后通过 Tasker 执行任务。 ⚠️ 重要:run_pipeline 不会自动把界面恢复到入口节点所假设的起始状态。 @@ -406,7 +447,12 @@ def run_pipeline( controller_id: str, pipeline_path: str, entry: Optional[str] = None, + resource_path: Optional[str] = None, ) -> TaskDetail | str: + # 如果传入了 resource_path,添加它以便 get_or_create_resource 加载该路径 + if resource_path: + add_resource_path(resource_path) + # 检查文件是否存在 path = Path(pipeline_path) if not path.exists(): @@ -450,7 +496,44 @@ def run_pipeline( if not task_detail: return "任务执行失败,无法获取执行详情" - return task_detail + # 解析状态为易读文字 + status = task_detail.status + if status.succeeded: + status_text = "succeeded" + elif status.failed: + status_text = "failed" + elif status.running: + status_text = "running" + elif status.pending: + status_text = "pending" + elif status.done: + status_text = "done" + else: + status_text = str(status) + + # 获取节点详情 + nodes_info = [] + if hasattr(task_detail, 'nodes') and task_detail.nodes: + for node in task_detail.nodes: + node_info = {} + if hasattr(node, 'recognition') and node.recognition: + node_info["recognition"] = { + "all_results": getattr(node.recognition, 'all_results', None) + } + if hasattr(node, 'name'): + node_info["name"] = node.name + nodes_info.append(node_info) + + result = { + "task_id": task_detail.task_id, + "entry": task_detail.entry, + "status": status_text, + "node_count": len(task_detail.node_id_list) if hasattr(task_detail, 'node_id_list') else 0, + } + if nodes_info: + result["nodes"] = nodes_info + + return result """ diff --git a/maa_mcp/resource.py b/maa_mcp/resource.py index 0b5e5f7..1b3580b 100644 --- a/maa_mcp/resource.py +++ b/maa_mcp/resource.py @@ -11,23 +11,74 @@ # 全局资源 ID 的固定键名 _GLOBAL_RESOURCE_KEY = "_global_resource" +# 资源路径列表(按加载顺序,后加载的会覆盖先加载的同名资源) +_resource_paths: list[str] = [] +# 记录默认路径是否已加载 +_default_loaded: bool = False +# 记录已加载的自定义路径(用于去重) +_loaded_paths: list[str] = [] + + +def add_resource_path(path: str): + """ + 添加资源路径,后加载的会覆盖先加载的同名资源。 + 添加后会立即加载该路径到已创建的 Resource。 + """ + global _resource_paths, _default_loaded, _loaded_paths + + if path not in _resource_paths: + _resource_paths.append(path) + + resource: Resource | None = object_registry.get(_GLOBAL_RESOURCE_KEY) + if not resource: + # Resource 还不存在,等 get_or_create_resource 时一起加载 + return + + # Resource 已存在,立即加载新路径 + # 先确保默认路径已加载 + if not _default_loaded: + default_path = str(get_resource_dir()) + resource.post_bundle(default_path) + _default_loaded = True + + # 只加载新增的自定义路径(去重) + if path not in _loaded_paths: + resource.post_bundle(str(path)) + _loaded_paths.append(path) + + +def clear_resource(): + """清除全局 Resource 缓存,强制重新创建。""" + global _resource_paths, _default_loaded, _loaded_paths + _resource_paths.clear() + _default_loaded = False + _loaded_paths.clear() + object_registry.unregister(_GLOBAL_RESOURCE_KEY) + def get_or_create_resource() -> Optional[Resource]: """ 获取或创建全局唯一的 Resource 实例。 注意:调用此函数前应确保 OCR 资源已存在,否则可能加载失败。 + + Resource 会按顺序加载多个资源路径,后加载的会覆盖先加载的同名资源。 """ + global _resource_paths, _default_loaded, _loaded_paths + resource: Resource | None = object_registry.get(_GLOBAL_RESOURCE_KEY) - if resource: - return resource + if not resource: + resource = Resource() + object_registry.register_by_name(_GLOBAL_RESOURCE_KEY, resource) - resource_path = get_resource_dir() + # 首次创建时加载所有路径 + default_path = str(get_resource_dir()) + resource.post_bundle(default_path) + _default_loaded = True - resource = Resource() - if not resource.post_bundle(str(resource_path)).wait().succeeded: - return None + for path in _resource_paths: + resource.post_bundle(str(path)) + _loaded_paths.append(path) - object_registry.register_by_name(_GLOBAL_RESOURCE_KEY, resource) return resource From 4137f87efbe70e31d7ad1640e902ea22468a04d7 Mon Sep 17 00:00:00 2001 From: KhazixW2 <3196497671@qq.com> Date: Tue, 24 Mar 2026 16:02:05 +0800 Subject: [PATCH 3/5] chore: update .mcp.json to use standard maa_mcp entry point --- .mcp.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mcp.json b/.mcp.json index 9f1ffae..2f2be01 100644 --- a/.mcp.json +++ b/.mcp.json @@ -4,7 +4,7 @@ "command": "python", "args": [ "-m", - "maa_mcp.pipeline_server" + "maa_mcp" ] } } From 1e18d0aafc8b5b113f998e3e3b50ddcbbff5e557 Mon Sep 17 00:00:00 2001 From: KhazixW2 <3196497671@qq.com> Date: Tue, 24 Mar 2026 16:03:01 +0800 Subject: [PATCH 4/5] =?UTF-8?q?chore:claude=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b1ea21b..6a51c4d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,10 @@ "mcp__maa-mcp__connect_adb_device", "mcp__maa-mcp__swipe", "mcp__maa-mcp__find_adb_device_list", - "mcp__maa-mcp__test_sweep" + "mcp__maa-mcp__test_sweep", + "mcp__maa-mcp__find_window_list", + "mcp__maa-mcp__get_current_datetime", + "mcp__maa-mcp__run_pipeline" ], "defaultMode": "bypassPermissions" }, @@ -28,4 +31,4 @@ "cwd": "${workspaceFolder}" } } -} \ No newline at end of file +} From 3d801d83bc1b1fdc058f5141d4660430f23b6d55 Mon Sep 17 00:00:00 2001 From: KhazixW2 <3196497671@qq.com> Date: Tue, 24 Mar 2026 16:38:31 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=92=8C=E6=B8=85=E7=90=86=E7=9A=84=E4=B8=A4?= =?UTF-8?q?=E4=B8=AAbuf=20risk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- maa_mcp/pipeline_tools.py | 3 +-- maa_mcp/resource.py | 18 +++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/maa_mcp/pipeline_tools.py b/maa_mcp/pipeline_tools.py index 496785f..989a83b 100644 --- a/maa_mcp/pipeline_tools.py +++ b/maa_mcp/pipeline_tools.py @@ -14,7 +14,6 @@ from typing import Optional from lzstring import LZString -from maa.tasker import TaskDetail from maa_mcp.core import mcp from maa_mcp.paths import get_data_dir @@ -448,7 +447,7 @@ def run_pipeline( pipeline_path: str, entry: Optional[str] = None, resource_path: Optional[str] = None, -) -> TaskDetail | str: +) -> dict | str: # 如果传入了 resource_path,添加它以便 get_or_create_resource 加载该路径 if resource_path: add_resource_path(resource_path) diff --git a/maa_mcp/resource.py b/maa_mcp/resource.py index 1b3580b..7d8e97f 100644 --- a/maa_mcp/resource.py +++ b/maa_mcp/resource.py @@ -1,5 +1,6 @@ from typing import Optional +from loguru import logger from maa.controller import Controller from maa.resource import Resource from maa.tasker import Tasker @@ -38,19 +39,20 @@ def add_resource_path(path: str): # 先确保默认路径已加载 if not _default_loaded: default_path = str(get_resource_dir()) - resource.post_bundle(default_path) + if not resource.post_bundle(default_path).wait().succeeded: + logger.warning(f"加载默认资源包失败: {default_path}") _default_loaded = True # 只加载新增的自定义路径(去重) if path not in _loaded_paths: - resource.post_bundle(str(path)) + if not resource.post_bundle(str(path)).wait().succeeded: + logger.warning(f"加载自定义资源包失败: {path}") _loaded_paths.append(path) def clear_resource(): - """清除全局 Resource 缓存,强制重新创建。""" - global _resource_paths, _default_loaded, _loaded_paths - _resource_paths.clear() + """清除全局 Resource 缓存,强制重新创建(保留已配置的资源路径)。""" + global _default_loaded, _loaded_paths _default_loaded = False _loaded_paths.clear() object_registry.unregister(_GLOBAL_RESOURCE_KEY) @@ -72,11 +74,13 @@ def get_or_create_resource() -> Optional[Resource]: # 首次创建时加载所有路径 default_path = str(get_resource_dir()) - resource.post_bundle(default_path) + if not resource.post_bundle(default_path).wait().succeeded: + logger.warning(f"加载默认资源包失败: {default_path}") _default_loaded = True for path in _resource_paths: - resource.post_bundle(str(path)) + if not resource.post_bundle(str(path)).wait().succeeded: + logger.warning(f"加载自定义资源包失败: {path}") _loaded_paths.append(path) return resource