claw-code 源码分析:Tool Pool 组装——默认策略、过滤、MCP 开关如何影响「可用工具面」?

涉及源码src/tool_pool.pysrc/tools.pysrc/permissions.pysrc/main.pysrc/system_init.py;衔接 result/08.mdresult/09.mdresult/05.md


1. 「可用工具面」在本仓库里的精确定义

这里说的不是哲学上的「模型理论上能用什么」,而是 某次调用 get_tools(...) / assemble_tool_pool(...) 得到的 PortingModule 元组

在那一刻,系统把哪些工具条目视为 允许进入池子(给 UI 列表、给报告、给未来的 tool schema 装配)。

全集 来自快照,进程内常量为 PORTED_TOOLS

python 复制代码
# 37:37:src/tools.py
PORTED_TOOLS = load_tool_snapshot()

池子 是在全集上做 一串确定性过滤 的结果;默认策略 即各开关取默认值、且无 ToolPermissionContext 时的结果。


2. 组装流水线:assemble_tool_poolget_tools

python 复制代码
# 28:37:src/tool_pool.py
def assemble_tool_pool(
    simple_mode: bool = False,
    include_mcp: bool = True,
    permission_context: ToolPermissionContext | None = None,
) -> ToolPool:
    return ToolPool(
        tools=get_tools(simple_mode=simple_mode, include_mcp=include_mcp, permission_context=permission_context),
        simple_mode=simple_mode,
        include_mcp=include_mcp,
    )

ToolPool 本身只 缓存策略参数 + 过滤后的元组 ,并用 Markdown 输出 总数与前 15 条(便于扫一眼,不全量刷屏):

python 复制代码
# 16:25:src/tool_pool.py
    def as_markdown(self) -> str:
        lines = [
            '# Tool Pool',
            '',
            f'Simple mode: {self.simple_mode}',
            f'Include MCP: {self.include_mcp}',
            f'Tool count: {len(self.tools)}',
        ]
        lines.extend(f'- {tool.name} --- {tool.source_hint}' for tool in self.tools[:15])
        return '\n'.join(lines)

CLI 默认报表python3 -m src.main tool-pool 调用 assemble_tool_pool() 无参 ,即 simple_mode=Falseinclude_mcp=Truepermission_context=None ------ 在代码能力上等于 「快照全集再过一遍权限过滤器(空)」 ,也就是 最大池 (与 get_tools() 默认一致)。


3. 过滤顺序与语义:get_tools 的三段闸门

python 复制代码
# 62:72:src/tools.py
def get_tools(
    simple_mode: bool = False,
    include_mcp: bool = True,
    permission_context: ToolPermissionContext | None = None,
) -> tuple[PortingModule, ...]:
    tools = list(PORTED_TOOLS)
    if simple_mode:
        tools = [module for module in tools if module.name in {'BashTool', 'FileReadTool', 'FileEditTool'}]
    if not include_mcp:
        tools = [module for module in tools if 'mcp' not in module.name.lower() and 'mcp' not in module.source_hint.lower()]
    return filter_tools_by_permission_context(tuple(tools), permission_context)

3.1 Simple mode:硬编码「三件套」

开启后 只保留 BashToolFileReadToolFileEditTool 三个 精确工具名
效果:可用工具面急剧收缩到「最小可讲清楚的 demo 面」,适合教学、沙箱、或先不接复杂工具生态。

边界 :名字不在集合内的「基础」工具也会被砍掉------这是 刻意极端 的产品策略占位,不是自动推断「安全工具」。

3.2 MCP 开关:子串启发式

include_mcp=False 时,丢弃满足以下任一条件的条目:

  • module.name.lower()mcp,或
  • module.source_hint.lower()mcp

效果 :把 MCP 相关工具族 从整体池里剥离(例如 MCPToolListMcpResourcesTool 等路径里带 mcp 的条目)。

边界 :这是 字符串启发式 ,不是协议层注册表;误杀/漏杀都可能在(例如路径改名不含 mcp 但实际是 MCP 工具)。成熟产品通常会改为 显式 kind / protocol 字段

3.3 权限上下文:按工具名 deny

python 复制代码
# 56:59:src/tools.py
def filter_tools_by_permission_context(tools: tuple[PortingModule, ...], permission_context: ToolPermissionContext | None = None) -> tuple[PortingModule, ...]:
    if permission_context is None:
        return tools
    return tuple(module for module in tools if not permission_context.blocks(module.name))
python 复制代码
# 18:20:src/permissions.py
    def blocks(self, tool_name: str) -> bool:
        lowered = tool_name.lower()
        return lowered in self.deny_names or any(lowered.startswith(prefix) for prefix in self.deny_prefixes)

效果 :被 block 的工具 不出现在池子里 (能力面收缩)。这与 PermissionDenial 事件是两条线:前者是 「列表里就没有」 ,后者是 「模型点了但被拒」 (见 result/05.md)。

顺序 :先 simple → 再 MCP → 再 permission。若 simple 已只剩 3 个工具,MCP 与 deny 只在这 3 个上继续过滤


4. CLI 如何把策略旋钮暴露给用户

tools 子命令把 与池子相同的参数 接到 get_tools

python 复制代码
# 40:46:src/main.py
    tools_parser = subparsers.add_parser('tools', help='list mirrored tool entries from the archived snapshot')
    tools_parser.add_argument('--limit', type=int, default=20)
    tools_parser.add_argument('--query')
    tools_parser.add_argument('--simple-mode', action='store_true')
    tools_parser.add_argument('--no-mcp', action='store_true')
    tools_parser.add_argument('--deny-tool', action='append', default=[])
    tools_parser.add_argument('--deny-prefix', action='append', default=[])
python 复制代码
# 132:140:src/main.py
    if args.command == 'tools':
        if args.query:
            print(render_tool_index(limit=args.limit, query=args.query))
        else:
            permission_context = ToolPermissionContext.from_iterables(args.deny_tool, args.deny_prefix)
            tools = get_tools(simple_mode=args.simple_mode, include_mcp=not args.no_mcp, permission_context=permission_context)
            output_lines = [f'Tool entries: {len(tools)}', '']
            output_lines.extend(f'- {module.name} --- {module.source_hint}' for module in tools[: args.limit])
            print('\n'.join(output_lines))
        return 0

注意 --query 分支 :走 render_tool_indexfind_tools,它在 PORTED_TOOLS 上搜索不应用 simple/MCP/permission 过滤。用途是「在全镜像里找关键词」,不是「当前池里找」。

测试 test_tool_permission_filtering_cli_runs 验证 --deny-prefix mcp 后列表中不再出现 MCPTool


5. 关键边界:池子 不等于 路由全宇宙

PortRuntime.route_prompt全量 PORTED_TOOLS 上匹配,而不是 get_tools() 过滤后的子集(见 result/08.mdresult/09.md)。

因此:

  • Tool Pool / tools 列表 描述的是 策略下的「展示给模型/用户的候选面」
  • 路由 仍可能在 策略禁止 的工具名上命中------产品化时需要 在路由后二次过滤只把池内名字暴露给模型

6. system_init 与池策略的错位(阅读源码时要留意)

build_system_init_message 使用 无参 get_tools()

python 复制代码
# 8:12:src/system_init.py
def build_system_init_message(trusted: bool = True) -> str:
    setup = run_setup(trusted=trusted)
    commands = get_commands()
    tools = get_tools()

因此 bootstrap 报告里的 "Loaded tool entries" 计数 对应 默认最大池 ,不一定等于你刚用 --simple-mode 在 CLI 里看到的列表。若未来要在报告里体现「当前会话策略」,需要把 同一套 get_tools 参数 贯穿 bootstrap_session / system_init


7. 小结表

开关 默认(tool-pool 对可用工具面的影响
simple_mode False True → 仅 3 个固定工具名
include_mcp True False → 去掉名字或路径含 mcp 的条目
permission_context None 非空 → 按精确名与前缀拒绝,条目从池中消失
Markdown 展示 前 15 条 不改变池大小,只影响输出长度

一句话 :Tool Pool 组装把 「可用工具面」变成可配置、可打印、可测试的函数 ;理解它的关键是 过滤顺序、启发式边界、以及与路由/查询路径是否共用同一过滤


相关推荐
I疯子2 小时前
2026-04-07 打卡第 4 天
python
Zzj_tju2 小时前
Java 从入门到精通(十二):File 与 IO 流基础,为什么程序“读写文件”时总是容易出问题?
java·python·php
汽车搬砖家2 小时前
vSOMEIP系列 -6: vsomeip python版部署,双机跨域通信(vsomeip - davinci AP someip)
python·汽车
小陈工2 小时前
Python Web开发入门(十六):前后端分离架构设计——从“各自为政”到“高效协同”
开发语言·前端·数据库·人工智能·python
gogogo出发喽3 小时前
使用Pear Admin Flask
后端·python·flask
与虾牵手3 小时前
Python asyncio 踩了一周坑,我把能犯的错全犯了一遍
python
飞Link3 小时前
LangGraph 核心架构解析:节点 (Nodes) 与边 (Edges) 的工作机制及实战指南
java·开发语言·python·算法·架构
beyond阿亮3 小时前
Claude Code零基础入门安装使用指南
人工智能·ai·claude code
程序消消乐3 小时前
第一章:Claude Code 记忆系统——架构总览与四种记忆类型
大数据·架构·agent·claude code