第 10 章:实战 — 工具生态

前两章我们造了 MiniClaw 的"骨架":认证、配置、引擎、记忆、命令行界面。 但说白了,它现在就是一个"能聊天的终端"——你说一句,它回一句,仅此而已。

这一章,我们给 MiniClaw 装技能。让它不只是聊天,还能做事

前提条件

  1. 已完成前面章节的学习,了解 @toolcreate_sdk_mcp_server 的用法
  2. 已安装 SDK:uv add claude-agent-sdk

1. 本章目标

想象一下你手机刚买来的时候——只能打电话发短信。装上微信就能聊天,装上高德就能导航, 装上支付宝就能付款。工具就是 MiniClaw 的"App Store"。

本章结束后,MiniClaw 将拥有 5 个内置技能:

技能 说明 举例
current_time 查看当前时间 "现在几点了?"
system_info 查看系统信息 "我的电脑配置怎么样?"
calculator 做数学计算 "帮我算 sin(45) + log(100)"
note_save 保存笔记 "帮我记一下:明天下午开会"
note_list 查看所有笔记 "我之前记了哪些笔记?"

更重要的是,我们会建一套工具注册机制——以后想加新技能, 写个函数、注册一下就完事了,不用改引擎代码。


2. 整体架构

加完工具后,MiniClaw 的文件结构:

miniclaw/
  __init__.py            # 包初始化
  __main__.py            # 入口:python -m miniclaw
  auth.py                # 认证管理
  config.py              # 配置管理
  engine.py              # 对话引擎(本章更新)
  memory.py              # 对话记忆
  cli.py                 # 命令行界面
  tools/                 # 新增:工具系统
    __init__.py           # 工具包入口
    registry.py           # 工具注册中心
    builtin.py            # 5 个内置工具

工作流程一目了然:

启动 MiniClaw
    ↓
创建 ToolRegistry(注册中心)
    ↓
register_builtin_tools()  ← 把 5 个内置工具注册进去
    ↓
registry.create_server()  ← 打包成 SDK MCP 服务器
    ↓
注入 engine.py            ← 引擎带着工具启动
    ↓
Claude 自动使用工具       ← 用户问"现在几点",Claude 调用 current_time

3. 工具注册中心 (tools/registry.py)

为什么需要注册中心?

你可能会想:我直接在 engine.py 里写死工具列表不行吗?

行,但不好。原因有三:

  1. 解耦 — 引擎不应该知道具体有哪些工具,它只需要知道"有工具可用"
  2. 可扩展 — 未来加新工具,只要往注册中心注册就行,不用改引擎
  3. 白名单管理 — 注册中心自动生成 allowed_tools,不用手动拼字符串

核心代码

完整代码见 miniclaw/tools/registry.py

ToolRegistry 只有 4 个方法,每个都很直白:

class ToolRegistry:
    def __init__(self, server_name: str = "miniclaw"):
        self.server_name = server_name
        self._tools: list[SdkMcpTool] = []

    def register(self, tool_def: SdkMcpTool) -> None:
        """注册一个工具。"""
        self._tools.append(tool_def)

    def create_server(self) -> McpSdkServerConfig:
        """打包成 SDK MCP 服务器。"""
        return create_sdk_mcp_server(name=self.server_name, tools=self._tools)

    def get_allowed_tools(self) -> list[str]:
        """获取工具白名单,格式为 mcp__<服务器名>__<工具名>。"""
        return [f"mcp__{self.server_name}__{t.name}" for t in self._tools]

说白了就是一个列表的封装。它的价值不在于代码复杂度,而在于建立了一个清晰的边界: 工具的定义、注册、打包都在 tools/ 包里搞定,引擎只管用。


4. 内置工具集 (tools/builtin.py)

完整代码见 miniclaw/tools/builtin.py

5 个工具,逐个讲解核心设计思路。

工具 1:current_time — 查看当前时间

@tool(
    "current_time",
    "获取当前日期和时间,包含年月日、时分秒、星期几",
    {"timezone": str},
    annotations=ToolAnnotations(readOnlyHint=True),
)
async def current_time(args: dict) -> dict:
    ...

设计要点: - readOnlyHint=True:查时间是纯读操作,不改任何东西 - 参数 timezone 是字符串,Claude 会根据用户的问法自动传合适的值 - 用标准库 timedelta 做时区偏移,不依赖 pytz

工具 2:system_info — 查看系统信息

@tool(
    "system_info",
    "获取当前系统信息,包括操作系统、CPU、内存、磁盘使用情况",
    {"detail_level": str},
    annotations=ToolAnnotations(readOnlyHint=True),
)

设计要点: - 同样 readOnlyHint=True——只读不写 - detail_level 参数让 Claude 选择 "basic""full" 模式 - 只用 platformosshutil 标准库,零额外依赖

工具 3:calculator — 数学计算器

@tool(
    "calculator",
    "计算数学表达式。支持加减乘除、幂运算、三角函数、对数等",
    {"expression": str},
)

设计要点: - 没标注 readOnlyHint——有意不加,让你对比"有注解"和"没注解"的区别 - 安全措施eval__builtins__ 设为空字典,只暴露白名单里的数学函数。 即使表达式里写了 os.system("rm -rf /"),也会报错而不是真的执行 - 用 try/except 兜住所有异常,不让工具崩掉

工具 4:note_save — 保存笔记

@tool(
    "note_save",
    "保存一条笔记到本地。笔记会存储在 ~/.miniclaw/notes/ 目录下",
    {"title": str, "content": str},
)

设计要点: - 这个工具会写文件,所以没标 readOnlyHint - 笔记存到 ~/.miniclaw/notes/,自动建目录 - 文件名用"时间戳 + 标题",不会覆盖旧笔记 - 标题做了特殊字符清理,避免文件名问题

工具 5:note_list — 列出所有笔记

@tool(
    "note_list",
    "列出所有已保存的笔记。可以按关键词筛选",
    {"keyword": str},
    annotations=ToolAnnotations(readOnlyHint=True),
)

设计要点: - readOnlyHint=True——只读不写 - keyword 参数支持按关键词筛选 - 笔记按时间倒序排列(最新的在前面)

ToolAnnotations 小结

5 个工具中,3 个标了 readOnlyHint=True,2 个没标。规律很简单:

工具 是否只读 有无注解
current_time 只读 readOnlyHint=True
system_info 只读 readOnlyHint=True
calculator 只读(但我们故意不标)
note_save 会写文件
note_list 只读 readOnlyHint=True

建议: 只读工具都标上 readOnlyHint=True,这是个好习惯。

注册函数

一个 register_builtin_tools() 函数,一键把 5 个工具注册进去:

def register_builtin_tools(registry: ToolRegistry) -> None:
    registry.register(current_time)
    registry.register(system_info)
    registry.register(calculator)
    registry.register(note_save)
    registry.register(note_list)

调用方只需要两行:

registry = ToolRegistry()
register_builtin_tools(registry)
# 5 个工具就绑好了

5. 集成到引擎

engine.py 的变化

之前创建 ClaudeAgentOptions 时没有工具,现在加两行:

options = ClaudeAgentOptions(
    system_prompt=system_prompt,
    permission_mode="bypassPermissions",
    max_turns=10,
    # ---- 新增 ----
    mcp_servers=mcp_servers,       # 工具服务器
    allowed_tools=allowed_tools,   # 工具白名单
)

引擎通过构造函数接收这两个参数,完全不需要知道具体有哪些工具。

main.py 的变化

入口文件负责"组装":

from miniclaw.tools import ToolRegistry, register_builtin_tools

registry = ToolRegistry()
register_builtin_tools(registry)

engine = ChatEngine(
    config=config,
    memory=memory,
    mcp_servers={"miniclaw": registry.create_server()},
    allowed_tools=registry.get_allowed_tools(),
)

注意 mcp_servers 的 key 必须和 ToolRegistryserver_name 一致(默认都是 "miniclaw")。 不一致的话 allowed_tools 里的名字就对不上,工具就用不了。


6. 用户自定义工具(扩展机制)

想给 MiniClaw 加新技能?比如查天气。两步搞定:

Step 1:写工具函数

# miniclaw/tools/weather.py
@tool("get_weather", "查询指定城市的天气信息", {"city": str},
      annotations=ToolAnnotations(readOnlyHint=True))
async def get_weather(args: dict) -> dict:
    city = args["city"]
    # 这里调用真正的天气 API
    return {"content": [{"type": "text", "text": f"{city}:晴,25°C"}]}

Step 2:注册一下

from miniclaw.tools.weather import get_weather
registry.register(get_weather)  # 加这一行

不用改引擎,不用改 CLI,不用改任何其他文件。 get_allowed_tools() 自动包含新工具。这就是注册中心的价值。


7. 运行效果

python -m miniclaw

查时间:

你: 现在几点了?
MiniClaw: 当前时间是 2026-02-25 14:32:18,星期三,时区 Asia/Shanghai (UTC+8)。

查系统:

你: 看看我电脑的配置
MiniClaw: 操作系统 Darwin 25.3.0,架构 arm64,Python 3.12.1,CPU 10 核心。

算数学:

你: 帮我算 sin(pi/4) + log10(1000)
MiniClaw: sin(pi/4) + log10(1000) = 0.7071 + 3 = 3.7071

记笔记 + 查笔记:

你: 帮我记一下,明天下午三点和老王开会
MiniClaw: 笔记已保存到 ~/.miniclaw/notes/20260225_143518_明天下午三点和老王开会.md

你: 我之前记了哪些笔记?
MiniClaw: 共 3 条笔记:
  - 明天下午三点和老王开会
  - 买菜清单
  - 项目 TODO

组合使用(最酷的部分):

你: 现在几点了?顺便帮我记一下,明天要交报告
MiniClaw: 现在是 14:35:22。已帮你记下"明天要交报告",保存到
~/.miniclaw/notes/20260225_143522_明天要交报告.md。

Claude 自动先调 current_time,再调 note_save,把结果整合成一句话。 你不需要告诉它"先查时间再记笔记"——它自己判断该用什么工具、什么顺序。


8. 小结

这一章做了三件事:

  1. 建了工具注册中心ToolRegistry 统一管理注册、打包、白名单
  2. 写了 5 个内置工具 — 时间、系统信息、计算器、保存笔记、查看笔记
  3. 集成到引擎 — 在 ClaudeAgentOptions 中注入 mcp_serversallowed_tools

核心收获:

下一章,我们将为 MiniClaw 添加钩子系统——在 Claude 调用工具前后插入安全检查。


本章文件清单

10-实战-工具生态/
  README.md                              # 你正在读的这个文件
  miniclaw/
    __init__.py                          # 包初始化
    __main__.py                          # 入口文件(集成工具系统)
    auth.py                              # 认证管理
    config.py                            # 配置管理
    engine.py                            # 对话引擎(已更新,支持工具注入)
    memory.py                            # 对话记忆
    cli.py                               # 命令行界面
    tools/
      __init__.py                        # 工具包入口
      registry.py                        # 工具注册中心
      builtin.py                         # 5 个内置工具