乐于分享
好东西不私藏

AI Agent 平台 CoPaw 源码学习:系统核心——MCP 和 Skills 模块

AI Agent 平台 CoPaw 源码学习:系统核心——MCP 和 Skills 模块

前言

大家好,我是高双喜。

前面咱们连着聊了三期 QwenPaw 源码:Agent 模块、记忆模块、浏览器自动化模块。

一个成熟的 AI Agent 平台,必须具备强大的扩展能力。QwenPaw 通过两个核心机制来实现:MCP(Model Context Protocol)模块负责集成外部工具,Skills 模块负责知识包管理。

今天咱们就把这两个模块一次聊透。


一、MCP 模块:外部工具集成

1.1 什么是 MCP?

MCP(Model Context Protocol)是 AI 工具集成的标准协议。QwenPaw 通过 StdIO 管道与 MCP 服务端进程通信,自动发现和注册服务端暴露的工具。Agent 在 ReAct 循环中可以像使用内置工具一样调用 MCP 工具。

简单理解:MCP 就是一个”工具市场”,各种外部服务通过 MCP 协议接入 QwenPaw,Agent 就能调用它们。

1.2 核心组件

组件
文件
职责
MCPClientManager
app/mcp/manager.py
MCP 客户端全生命周期管理
MCPConfigWatcher
app/mcp/watcher.py
配置变更检测 + 热重载
MCPStatefulClient
app/mcp/stateful_client.py
StdIO 有状态客户端封装
MCPClientConfig
config/config.py
单个 MCP 客户端配置模型
MCPConfig
config/config.py
MCP 全局配置(客户端字典)
MCP API Router
app/routers/mcp.py
REST API 端点

1.3 配置模型

# MCPClientConfigclassMCPClientConfig(BaseModel):    name: str# 客户端显示名称    description: str# 描述信息    enabled: bool# 是否启用    command: str# MCP 服务端启动命令(如 "npx", "python")    args: List[str]     # 命令行参数    env: Dict[strstr# 环境变量

默认内置了一个客户端 tavily_search,仅在 TAVILY_API_KEY 环境变量存在时自动启用:

# config.py 默认配置clients: Dict[str, MCPClientConfig] = Field(    default_factory=lambda: {"tavily_search": MCPClientConfig(            name="tavily_mcp",            enabled=bool(os.getenv("TAVILY_API_KEY")),  # 自动检测            command="npx",            args=["-y""tavily-mcp@latest"],            env={"TAVILY_API_KEY": os.getenv("TAVILY_API_KEY""")},        ),    },)

1.4 MCPClientManager 核心方法

方法
说明
init_from_config(config)
批量初始化,跳过 enabled=False,单个失败不中断
get_clients()
返回所有活跃客户端列表(每次查询调用)
get_client(key)
获取单个客户端(按 key)
replace_client(key, config)
热替换:锁外连接新客户端 → 锁内替换 → 关闭旧客户端
remove_client(key)
移除并关闭指定客户端
close_all()
关闭所有客户端(应用关停时)

这里有个很巧妙的设计:replace_client 两阶段操作

阶段 1 (锁外,可能耗时):    new_client = StdIOStatefulClient(name, command, args, env)    await asyncio.wait_for(new_client.connect(), timeout=60.0)阶段 2 (锁内,原子操作):    async with _lock:        old_client = _clients.get(key)        _clients[key] = new_client        if old_client:            await old_client.close()

锁外连接、锁内替换,避免长时间持锁导致并发下降。

1.5 热重载机制

MCPConfigWatcher 每 2 秒轮询一次 config.json:

_poll_loop → _check():1. 检查 config.json mtime → 未变则跳过 (快速拒绝)2. 加载配置,计算 mcp section hash → 相同则跳过3. 检查 _reload_task 是否在进行 → 是则跳过4. 创建后台 asyncio.Task → _reload_changed_clients()

Diff 处理逻辑:

场景
处理
新增客户端
mcp_manager.replace_client(key, new_cfg)
配置变更
mcp_manager.replace_client(key, new_cfg)
enabled: true → false
mcp_manager.remove_client(key)
客户端被删除
mcp_manager.remove_client(key)

重试机制也很完善:同一配置 hash 最多 3 次重试,配置变更时自动重置计数,重载失败不阻塞主流程。

1.6 Agent 集成

每次查询的流程:

# AgentRunner.query_handler() - 每次查询mcp_clients = await mcp_manager.get_clients()  # 获取最新列表agent = QwenPawAgent(..., mcp_clients=mcp_clients, ...)  # 注入到 Agentawait agent.register_mcp_clients()  # 注册到 Toolkit# Agent ReAct 循环中自动发现并调用 MCP 工具

热重载原理很简单:每次查询创建新 QwenPawAgent 实例 → 从 get_clients() 获取最新客户端列表。MCPConfigWatcher 更新管理器中的客户端后,下一次查询自动使用新客户端,无需重启应用。

1.7 REST API

方法
路径
说明
GET
/api/mcp
列出所有客户端
GET
/api/mcp/{key}
获取客户端详情
POST
/api/mcp
创建客户端
PUT
/api/mcp/{key}
更新客户端(env 合并模式)
PATCH
/api/mcp/{key}/toggle
切换启用/禁用
DELETE
/api/mcp/{key}
删除客户端

安全特性:API 响应自动掩码 env 值(如 sk-****1234),防止 API Key 泄露。


二、Skills 模块:知识包管理

2.1 Skills 与 Tools 的区别

这是两个层次的概念:

  • Tools(工具层):Python 异步函数,实现原子操作(文件读写、Shell 执行、浏览器控制等),直接注册到 agentscope 的 Toolkit
  • Skills(技能层):Markdown 知识包(SKILL.md),通过 YAML Front Matter + Markdown 正为 Agent 注入领域知识,指导如何组合使用 Tools

核心设计理念:Skills 不是插件代码,而是 Prompt 注入式的知识包。每个 Skill 本质上是一份结构化的 Markdown 文档,Agent 在回答用户问题时参考这些文档来决定使用哪些 Tools 以及如何使用。

2.2 目录结构

⚠️ 修正:源码实际位于 QwenPaw/agents/(venv site-packages 下),而非 src/QwenPaw/agents/

QwenPaw/agents/├── tools/                    # 工具层(Python 异步函数)│   ├── browser_control.py    # 浏览器自动化(3481 行)│   ├── browser_snapshot.py   # 浏览器快照/无障碍树解析│   ├── desktop_screenshot.py # 桌面截图│   ├── file_io.py           # 文件读写编辑(395 行)│   ├── file_search.py       # 文件搜索(grep/glob)│   ├── get_current_time.py  # 获取当前时间│   ├── memory_search.py     # 记忆语义搜索│   ├── send_file.py         # 向用户发送文件│   ├── shell.py             # Shell 命令执行│   └── view_media.py        # 图片/视频查看├── skills/                   # 技能层(Markdown 知识包,15 个)│   ├── browser_cdp/        # CDP 浏览器控制│   ├── browser_visible/    # 可见浏览器模式│   ├── channel_message/    # 频道消息│   ├── QwenPaw_source_index/ # QwenPaw 源码索引│   ├── cron/                # 定时任务管理│   ├── dingtalk_channel/   # 钉钉频道配置│   ├── docx/                # Word 文档处理│   ├── file_reader/         # 文件阅读│   ├── guidance/            # 安装配置引导│   ├── himalaya/            # 邮件 CLI 管理│   ├── multi_agent_collaboration/ # 多 Agent 协作│   ├── news/                # 新闻查询│   ├── pdf/                 # PDF 处理│   ├── pptx/                # PPT 演示文稿│   └── xlsx/                # Excel 电子表格├── skills_manager.py        # 技能管理器(2620 行)├── skills_hub.py            # 技能市场客户端(1691 行)└── react_agent.py           # Agent 主类(集成点)

2.3 Tools 工具层

工具注册机制

所有工具通过 __init__.py 统一导出,分为两类来源:

  1. agentscope 内置工具(3 个):

    • execute_python_code — Python 代码执行
    • view_text_file — 查看文本文件
    • write_text_file — 写入文本文件
  2. QwenPaw 自定义工具(16 个):

    • read_file / write_file / edit_file / append_file — 文件 I/O
    • grep_search / glob_search — 文件搜索
    • execute_shell_command — Shell 命令执行
    • send_file_to_user — 文件推送
    • browser_use — 浏览器自动化
    • desktop_screenshot — 桌面截图
    • view_image / view_video — 媒体查看
    • create_memory_search_tool — 记忆搜索(工厂模式)
    • get_current_time / set_user_timezone — 时间相关
    • get_token_usage — Token 用量统计

工具接口规范

from agentscope.tool import ToolResponsefrom agentscope.message import TextBlockasyncdeftool_function(param1: type, ...) -> ToolResponse:"""工具描述(docstring 即 LLM 的 function description)。    Args:        param1 (`type`): 参数说明。    """# 实现逻辑return ToolResponse(        content=[TextBlock(type="text", text="结果文本")]    )

关键约定:

  • 所有工具函数为 async 异步函数
  • 返回值必须是 ToolResponse 对象
  • 函数的 docstring 直接作为 LLM 调用时的工具描述
  • 参数类型注解将映射为 JSON Schema 供 LLM 使用

2.4 Skills 技能层

SKILL.md 文件格式

每个技能是一个目录,必须包含 SKILL.md 文件。格式为 YAML Front Matter + Markdown:

---name:skill_name# 必填:技能标识名description:"技能描述..."# 必填:LLM 匹配触发的描述homepage:https://...# 可选:项目主页license:Proprietary# 可选:许可证声明metadata:QwenPaw:# 或 openclawemoji:"📄"# 展示图标requires:bins: ["himalaya"]     # 需要的可执行文件install:-id:brewkind:brewformula:himalayabins: ["himalaya"]---# 技能标题正文内容:操作指南、代码示例、参考表格等Agent会将此内容作为上下文来理解如何完成相关任务

内置技能清单(共 15 个)

技能名
说明
附加资源
browser_cdp
CDP 浏览器控制(调试端口连接)
browser_visible
可见浏览器模式(headed 模式启动)
channel_message
频道消息发送
QwenPaw_source_index
QwenPaw 源码索引查询
cron
定时任务管理(CLI 命令指南)
dingtalk_channel
钉钉频道自动接入流程
docx
Word 文档创建/编辑/分析
scripts/(5个), LICENSE.txt
file_reader
文本文件读取与摘要
guidance
安装与配置引导
himalaya
邮件 CLI 管理(IMAP/SMTP)
references/
multi_agent_collaboration
多 Agent 协作与通信
news
新闻查询(多类别/多来源)
pdf
PDF 全功能处理
scripts/(8个), forms.md, reference.md, LICENSE.txt
pptx
PPT 演示文稿处理
scripts/(4个), editing.md, pptxgenjs.md, LICENSE.txt
xlsx
Excel 电子表格处理
scripts/(2个), LICENSE.txt

2.5 三级目录体系

层级
目录
路径
说明
内置 (builtin)
代码内嵌
QwenPaw/agents/skills/
随代码分发,不可修改
自定义 (customized)
用户目录
~/.QwenPaw/customized_skills/
用户创建/修改/Hub 导入
激活 (active)
运行目录
~/.QwenPaw/active_skills/
Agent 实际加载位置

优先级:customized > builtin(同名时自定义技能覆盖内置)

2.6 SkillsManager 核心方法

# SkillService 静态方法list_all_skills()                    # 列出 builtin + customized 所有技能list_available_skills()               # 列出 active 目录中已激活的技能create_skill()                       # 在 customized 目录创建新技能disable_skill()                      # 从 active 目录删除(禁用)enable_skill()                       # 同步到 active 目录(启用)delete_skill()                       # 从 customized 目录永久删除sync_from_active_to_customized()    # 反向同步load_skill_file()                    # 加载 references/ 或 scripts/ 中的文件

2.7 SkillsHub:在线技能市场

SkillsHub 是 QwenPaw 的在线技能市场客户端,支持从多个来源搜索和安装技能:

  1. ClawHub (clawhub.ai) — QwenPaw 官方技能市场
  2. Skills.sh (skills.sh) — 基于 GitHub 仓库的技能注册表
  3. GitHub (github.com) — 直接从 GitHub 仓库导入
  4. SkillsMP (skillsmp.com) — 第三方技能市场

URL 来源解析策略

install_skill_from_hub(bundle_url)├── _extract_skills_sh_spec(url) → (owner, repo, skill)├── _extract_github_spec(url) → (owner, repo, branch, path_hint)├── _extract_skillsmp_slug(url) → slug├── _resolve_clawhub_slug(url) → slug└── 兜底: 直接 JSON GET

2.8 Agent 集成点

classQwenPawAgent(ReActAgent):def__init__(self, ...):        toolkit = self._create_toolkit()        # 步骤 1: 创建工具包self._register_skills(toolkit)          # 步骤 2: 注册技能        sys_prompt = self._build_sys_prompt()   # 步骤 3: 构建提示词super().__init__(..., toolkit=toolkit)  # 步骤 4: 初始化 ReAct Agent

工具注册(实际注册 19 个工具):

def_create_toolkit(self) -> Toolkit:    toolkit = Toolkit()# agentscope 内置(3 个)    toolkit.register_tool_function(execute_python_code)    toolkit.register_tool_function(view_text_file)    toolkit.register_tool_function(write_text_file)# QwenPaw 自定义(16 个)    toolkit.register_tool_function(execute_shell_command)    toolkit.register_tool_function(read_file)    toolkit.register_tool_function(write_file)    toolkit.register_tool_function(edit_file)    toolkit.register_tool_function(append_file)    toolkit.register_tool_function(grep_search)    toolkit.register_tool_function(glob_search)    toolkit.register_tool_function(send_file_to_user)    toolkit.register_tool_function(desktop_screenshot)    toolkit.register_tool_function(view_image)    toolkit.register_tool_function(view_video)    toolkit.register_tool_function(browser_use)    toolkit.register_tool_function(get_current_time)    toolkit.register_tool_function(set_user_timezone)    toolkit.register_tool_function(get_token_usage)# create_memory_search_tool 是工厂函数,单独处理return toolkit

技能注册:

def_register_skills(self, toolkit: Toolkit):    ensure_skills_initialized()    working_skills_dir = get_working_skills_dir()  # ~/.QwenPaw/active_skills/    available_skills = list_available_skills()for skill_name in available_skills:        skill_dir = working_skills_dir / skill_nameif skill_dir.exists():            toolkit.register_agent_skill(str(skill_dir))

register_agent_skill() 会读取 SKILL.md,解析 YAML Front Matter 获取 name 和 description,将 Markdown 正文作为知识上下文注入 Agent 的系统提示。


三、架构模式总结

3.1 Skills 与 Tools 关系

┌─────────────────────────────────────────────────────┐│ QwenPawAgent ││ ┌───────────────┐ ┌────────────────────────────┐ ││ │ Toolkit │ │ System Prompt │ ││ │ (agentscope) │ │ (AGENTS.md + SOUL.md + │ ││ │ │ │ Skills 知识注入) │ ││ │ Tools: │ │ │ ││ │ • shell │ │ Skills 注入后 Agent 知道: │ ││ │ • read_file │ │ • 如何用 browser_use 查新闻 │ ││ │ • write_file │ │ • 如何用 himalaya 管邮件 │ ││ │ • edit_file │ │ • 如何用 shell 管 cron │ ││ │ • browser_use│ │ • 如何创建 docx/pdf/pptx │ ││ │ • screenshot │ │ • 如何进行 multi_agent │ ││ │ • send_file │ │ • 如何用 CDP 控制浏览器 │ ││ │ • get_time │ │ • ... │ ││ └───────────────┘ └────────────────────────────┘ │└─────────────────────────────────────────────────────┘

3.2 技能生命周期

┌──────────────┐│ 代码内置 ││ (builtin) │└──────┬───────┘│ sync_skills_to_working_dir()┌──────────────┐ ││ Hub 安装 │ ──→ ▼│ (online) │ ┌──────────────┐ ┌──────────────┐└──────────────┘ │ 自定义 │ ──→ │ 激活运行 │┌──────────────┐ │ (customized) │ │ (active) ││ CLI 创建 │ ──→ │ │ ││ API 创建 │ 覆盖 builtin │ │ Agent 加载此 │└──────────────┘ └──────────────┘ └──────────────┘▲ ││ sync_from_active... │└─────────────────────┘

总结

QwenPaw 的 MCP 和 Skills 模块设计得相当精妙:

MCP 模块亮点

  1. StdIO 通信:通过进程间标准输入输出通信,稳定可靠
  2. 热重载:每 2 秒检测配置变更,客户端独立替换
  3. 并发安全:锁外连接 + 锁内替换,避免长持锁
  4. 容错设计:初始化失败不中断、重载失败不阻塞

Skills 模块亮点

  1. Prompt 注入:用 Markdown 文档作为知识包,不含可执行代码
  2. 三级分层:builtin → customized → active,支持覆盖和隔离
  3. 多来源 Hub:支持 ClawHub、Skills.sh、GitHub、SkillsMP 四种安装来源
  4. 文件驱动:技能发现完全基于目录结构和 SKILL.md 文件存在性

数据一览

  • MCP 模块:manager.py (266 行) + watcher.py (330 行) + stateful_client.py (600+ 行)
  • Skills 模块:skills_manager.py (2620 行) + skills_hub.py (1691 行)
  • 工具总数:19 个(3 个 agentscope 内置 + 16 个 QwenPaw 自定义)
  • 内置技能:15 个

这两个模块共同构成了 QwenPaw 的能力扩展体系:MCP 负责接入外部服务,Skills 负责注入领域知识。有了它们,Agent 才能”上知天文地理,下知鸡毛蒜皮”。

好了,MCP 和 Skills 模块就聊到这里。觉得有帮助的点个赞、在看,转发给需要的朋友。咱们下期见。