Skip to content

工具与 MCP 🔥 ​

AgentScope 2.x 把"Agent 能做什么"全部收敛到 Toolkit。无论是 Python 函数、MCP 服务还是 Agent Skills,都经过同一套注册、命名、权限拦截与执行管线。Agent 不需要关心工具来源,只需要调用。本页 讲清 Toolkit 的统一装配方式、内置工具、三种自定义工具路径(FunctionTool / ToolBase / MCP), 以及 ToolGroup 按需激活机制。

版本基线:本文以 Python agentscope 2.0.4(PyPI,2026-07-07)为准,要求 Python >=3.11。 导入路径已与 main 分支源码核对: from agentscope.tool import Toolkit, ToolBase, FunctionTool, MCPTool, ToolGroup, Bash, Read, Write, Edit, Glob, Grep, ResetTools, ToolResponse, ToolChunk (源:src/agentscope/tool/__init__.py); from agentscope.mcp import MCPClient, StdioMCPConfig, HttpMCPConfig (源:src/agentscope/mcp/__init__.py); from agentscope.skill import Skill, LocalSkillLoader (源:src/agentscope/skill/__init__.py)。2.x 文档站存在版本化陷阱:非版本化的 /en/ URL 会 404,本页所有官方链接均使用 versions/2.0.4/en/ 形式。

Toolkit 统一装配 ​

Toolkit 是工具系统的唯一入口。它的构造函数接受三类来源,全部归入一个默认的 "basic" 工具组:

python
from agentscope.tool import Toolkit, FunctionTool, Bash, Read
from agentscope.mcp import MCPClient, HttpMCPConfig

toolkit = Toolkit(
    tools=[Bash(), Read(), FunctionTool(func=get_weather)],
    mcps=[amap_client],
    skills_or_loaders=["./my_skills"],
)
参数类型职责
toolslist[ToolBase]Python 工具对象(内置工具、FunctionTool、自定义 ToolBase)
mcpslist[MCPClient]MCP 客户端,工具列表由服务端动态发现
skills_or_loaders`list[strSkill
tool_groupslist[ToolGroup]额外的按需激活工具组(见下文)

三个参数都可选。不传任何一个,Toolkit 就是空的。传了任意组合,Toolkit 会把它们统一装进一个 名为 "basic" 的内部 ToolGroup,Agent 默认只能看到 basic 组里的工具。

前端类比 ​

如果你来自前端,可以把 Toolkit 类比为按需聚合的 npm 包:Python 函数、MCP 服务、Skills 都是被它"安装"进来的依赖,Agent 只看到统一的工具接口,不关心依赖是从哪来的 registry。

AgentScope 原生语义:Toolkit 不是包管理器,而是一个运行时工具注册表。它维护工具名到 ToolBase 实例的映射,负责在 ReAct 循环中按名字查找工具、注入 Agent 状态、执行并收集 ToolChunk 流。工具的 JSON Schema 由 ToolBase 子类自行暴露(FunctionTool 从函数签名自动 提取,MCPTool 从 MCP 服务端获取),Toolkit 只做聚合与过滤。

内置工具 ​

agentscope.tool 开箱提供六个文件系统与 Shell 工具,可直接实例化后放入 tools 列表:

工具用途
Bash执行 Shell 命令,支持流式输出与超时控制
Read读取文件内容,支持文本与图片
Write写入文件(覆盖或新建)
Edit按旧字符串精确替换文件内容
Glob按通配符模式匹配文件路径
Grep在文件内容中搜索正则或文本模式
python
from agentscope.tool import Toolkit, Bash, Read, Write, Edit, Glob, Grep

toolkit = Toolkit(
    tools=[Bash(), Read(), Write(), Edit(), Glob(), Grep()],
)

内置工具的 check_permissions 各自实现了细粒度规则(如 Bash 区分只读命令与写入命令、Write 检查路径是否在允许的工作目录内)。这些规则的细节在 权限拦截 中展开。

FunctionTool:包装 Python 函数 ​

FunctionTool 是把普通 Python 函数变成工具的最快路径。它自动从函数的 类型注解 提取入参 JSON Schema,从 docstring 提取描述。无需手写 Schema。

python
from agentscope.tool import Toolkit, FunctionTool


def get_weather(city: str, unit: str = "celsius") -> dict:
    """Get the current weather for a city.

    Args:
        city: The name of the city, e.g. "Hangzhou".
        unit: Temperature unit, "celsius" or "fahrenheit".

    Returns:
        A dict with temperature and condition.
    """
    # 实际场景中调用天气 API,这里返回占位数据
    return {"city": city, "temperature": 23, "unit": unit, "condition": "sunny"}


toolkit = Toolkit(
    tools=[FunctionTool(func=get_weather)],
)

FunctionTool 的构造参数:

参数类型默认值说明
funcCallable必填被包装的 Python 函数
namestr | NoneNone工具名,默认用函数名
descriptionstr | NoneNone工具描述,默认从 docstring 提取
is_concurrency_safeboolTrue是否可并发调用
is_read_onlyboolFalse是否只读(影响权限决策)
is_state_injectedboolFalse是否需要注入 Agent 状态(见下文)
middlewareslist[ToolMiddlewareBase] | NoneNone工具级中间件

默认 ASK 权限 ​

FunctionTool 的 check_permissions 默认返回 ASK,即每次调用都需要用户确认。这是安全兜底: 自定义函数可能有任意副作用,框架不会替你放行。如果你确定某个函数安全,可以自定义 ToolBase 并在 check_permissions 中返回 ALLOW(见下文),或在 Permission 层配置规则(见 权限拦截)。

自定义 ToolBase ​

当 FunctionTool 不够用--比如需要自定义权限逻辑、需要注入 Agent 状态、或需要流式输出--可以直接 继承 ToolBase,实现两个方法:

  • check_permissions(tool_input, context) -> PermissionDecision:权限决策。
  • call(**kwargs) -> ToolChunk | AsyncGenerator[ToolChunk, None]:工具执行逻辑。
python
from agentscope.permission import PermissionContext, PermissionDecision, PermissionBehavior
from agentscope.tool import ToolBase, Toolkit, ToolChunk
from agentscope.message import TextBlock, ToolResultState


class LookupUserTool(ToolBase):
    """按 user_id 查询用户信息。"""

    name = "lookup_user"
    description = "Look up a user by their ID. Returns name and email."
    input_schema = {
        "type": "object",
        "properties": {
            "user_id": {"type": "string", "description": "The user ID to look up."},
        },
        "required": ["user_id"],
    }
    is_concurrency_safe = True
    is_read_only = True

    async def check_permissions(
        self,
        tool_input: dict,
        context: PermissionContext,
    ) -> PermissionDecision:
        # 只读工具,直接放行
        return PermissionDecision(
            behavior=PermissionBehavior.ALLOW,
            message="Read-only lookup, allowing.",
        )

    async def call(self, **kwargs) -> ToolChunk:
        user_id = kwargs["user_id"]
        # 实际场景中查询数据库
        result = {"id": user_id, "name": "Alice", "email": "alice@example.com"}
        return ToolChunk(
            content=[TextBlock(text=str(result))],
            state=ToolResultState.RUNNING,
        )


toolkit = Toolkit(tools=[LookupUserTool()])

ToolBase 的关键类属性:

属性类型说明
namestr暴露给 Agent 的工具名
descriptionstrAgent 可读的工具描述
input_schemadict入参 JSON Schema
is_concurrency_safebool是否可并发调用
is_read_onlybool是否只读,影响权限决策
is_state_injectedbool是否需要注入 Agent 状态(注入参数名 _agent_state)
is_mcpbool是否为 MCP 工具(由 MCPTool 自动设置,自定义时保持 False)

call 的返回值支持两种形态:单个 ToolChunk 或 AsyncGenerator[ToolChunk, None](流式)。

ToolGroup 与 ResetTools ​

当工具数量很多时,把所有工具同时暴露给模型会浪费上下文并增加误调用风险。ToolGroup 解决这个问题: 工具被分组,只有被激活的组里的工具对 Agent 可见。

python
from agentscope.tool import Toolkit, ToolGroup, Bash, Read, FunctionTool

toolkit = Toolkit(
    # basic 组:始终激活
    tools=[Bash(), Read()],
    tool_groups=[
        ToolGroup(
            name="research",
            description="Activate to use web search and scraping tools.",
            tools=[FunctionTool(func=web_search), FunctionTool(func=scrape_url)],
        ),
        ToolGroup(
            name="data_analysis",
            description="Activate to use data analysis tools.",
            tools=[FunctionTool(func=run_sql), FunctionTool(func=plot_chart)],
        ),
    ],
)

运行时行为:

  1. basic 组始终激活。tools / mcps / skills_or_loaders 三个构造参数里的内容都进 basic 组。
  2. 非 basic 组默认未激活。Agent 初始看不到它们里面的工具。
  3. ResetTools 元工具自动注册。当 Toolkit 中存在至少一个非 basic 组时,Toolkit 自动注册一个名为 reset_tools 的内置元工具。Agent 调用它可以激活或停用工具组: reset_tools(research=True, data_analysis=False)。
  4. "basic" 是保留名。不能在 tool_groups 里传入 name="basic" 的组,否则会抛 ValueError。

这让 Agent 可以按需"加载"工具集:先在 basic 组里完成初步分析,再通过 reset_tools 激活 research 组做深度搜索,完成后切回 basic 组释放上下文。

MCP 集成 ​

AgentScope 2.x 通过 MCPClient 统一接入 MCP(Model Context Protocol)服务。MCP 工具不需要手写 Schema,由服务端动态发现。

两种连接模式 ​

模式配置类型是否需要 connect()适用场景
StatelessHttpMCPConfig否HTTP MCP 服务,每次调用建临时会话
StatefulStdioMCPConfig是STDIO MCP 服务,需启动子进程
StatefulHttpMCPConfig是HTTP MCP 服务,需保持长连接

关键规则:STDIO 必须是 Stateful。在 MCPClient 构造时,如果 mcp_config 是 StdioMCPConfig 但 is_stateful=False,会直接抛 ValueError。Stateful 客户端必须先 await client.connect() 再 传入 Toolkit,否则 Toolkit 构造时会抛 ValueError: The MCP client '{name}' is stateful, but not connected.。

Stateless HTTP 示例(高德地图 MCP) ​

python
import os

from agentscope.mcp import MCPClient, HttpMCPConfig
from agentscope.tool import Toolkit

amap_api_key = os.environ["AMAP_API_KEY"]

amap_client = MCPClient(
    name="amap",
    is_stateful=False,
    mcp_config=HttpMCPConfig(
        url=f"https://mcp.amap.com/sse?key={amap_api_key}",
    ),
    execution_timeout=30.0,
)

# Stateless:无需 connect(),直接传入 Toolkit
toolkit = Toolkit(mcps=[amap_client])

Stateful STDIO 示例(文件系统 MCP) ​

python
import asyncio

from agentscope.mcp import MCPClient, StdioMCPConfig
from agentscope.tool import Toolkit

fs_client = MCPClient(
    name="filesystem",
    is_stateful=True,
    mcp_config=StdioMCPConfig(
        command="npx",
        args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp/workspace"],
    ),
)

# Stateful:必须先 connect()
asyncio.run(fs_client.connect())

toolkit = Toolkit(mcps=[fs_client])

# 使用完毕后关闭连接
# asyncio.run(fs_client.close())

工具命名空间 ​

MCP 工具的模型可见名遵循 mcp__{server_name}__{tool_name} 格式。例如 amap 服务端暴露的 maps_weather 工具,在 Agent 视角是 mcp__amap__maps_weather。这个命名空间避免了不同 MCP 服务 之间的工具名冲突。server_name 即 MCPClient.name 字段,必须匹配 ^[a-zA-Z0-9_-]+$(LLM 提供商 对工具名的约束)。

MCPClient 字段一览 ​

字段类型说明
namestrMCP 服务名,用于工具命名空间
is_statefulbool是否需要显式 connect() / close()
mcp_configStdioMCPConfig | HttpMCPConfigMCP 服务配置(按 type 字段判别)
enable_toolslist[str] | None白名单,只暴露指定工具
disable_toolslist[str] | None黑名单,过滤指定工具
execution_timeoutfloat | None工具调用超时(秒)

enable_tools 与 disable_tools 不能同时包含同一个工具名。两者都为 None 时暴露服务端全部工具。

Skills 预览 ​

Skills 是 AgentScope 2.x 的另一类能力扩展。与工具不同,Skills 不是 Agent 直接调用的函数,而是 一组指令、脚本与资源,Agent 需要先通过内置的 SkillViewer 元工具阅读 Skill 的说明,再按说明使用 其中的工具与资源。

python
from agentscope.skill import LocalSkillLoader
from agentscope.tool import Toolkit

toolkit = Toolkit(
    skills_or_loaders=[
        LocalSkillLoader(directory="./my_skills", scan_subdir=True),
    ],
)

Toolkit 检测到 Skills 后会自动注册 SkillViewer 元工具,并在系统提示中注入 Skills 索引。Skills 的 完整用法(目录结构、Skill 编写、加载器扩展)在 Agent Skills 中展开。

与 Permission 的关系 ​

每次工具调用在执行前都经过 Permission 三态拦截(allow / deny / ask):

Agent 产出 ToolCallBlock
  -> Toolkit.call_tool 查找工具
  -> tool.check_permissions(tool_input, context)
  -> PermissionDecision: allow / deny / ask
  -> allow: 执行工具
  -> deny:  生成错误 ToolResult 回灌
  -> ask:   产出 HITL 确认事件,暂停 Agent

不同工具的默认权限策略:

工具来源默认权限行为
FunctionToolASK(每次调用需用户确认)
MCPTool只读工具 ALLOW,非只读 ASK
内置工具各自实现细粒度规则(如 Bash 按命令判别)
自定义 ToolBase由开发者在 check_permissions 中决定

权限规则、路径匹配、PermissionContext 与 PermissionMode 的完整说明见 权限拦截。

常见错误 ​

  • Stateful MCP 未 connect 就传入 Toolkit:Toolkit 构造时会遍历所有 MCP 客户端,对 Stateful 客户端检查 is_connected。未 connect() 会抛 ValueError: The MCP client '{name}' is stateful, but not connected.。解决:在传入 Toolkit 前 await client.connect()。
  • STDIO 配成 Stateless:StdioMCPConfig 必须搭配 is_stateful=True,否则 MCPClient 构造时直接抛 ValueError: STDIO MCP must be stateful (is_stateful=True).。STDIO 需要启动子 进程,无法做到无状态。
  • 函数无类型注解:FunctionTool 从类型注解自动提取 JSON Schema。如果函数参数没有注解 (如 def foo(x, y): 而非 def foo(x: str, y: int):),生成的 Schema 会缺失类型信息,模型 可能传错参数或拒绝调用。务必为每个参数标注类型。
  • 权限默认 ASK 卡住自动化:FunctionTool 默认 ASK,在无人工干预的自动化场景中会暂停 Agent 等待确认。解决方案三选一:自定义 ToolBase 并在 check_permissions 返回 ALLOW; 在 Permission 层配置规则放行;或使用内置工具(已有细粒度规则)。
  • MCP name 含非法字符:MCPClient.name 必须匹配 ^[a-zA-Z0-9_-]+$。如果用了点号(如 "my.server"),构造时会抛 ValueError,因为工具命名空间 mcp__{name}__{tool} 需要满足 LLM 提供商的命名约束。
  • ToolGroup 重名或用了 "basic":tool_groups 里的组名不能重复,也不能叫 "basic"(保留名)。 重复会抛 ValueError: Tool groups must not contain duplicate tool groups.,使用 "basic" 会抛 ValueError: The 'basic' tool group is reserved...。

先修 ​

  • Agent 核心 - 理解 ReAct 循环如何调用工具、ToolCallBlock 与 ToolResult 的回灌机制

下一步 ​

  • 结构化输出 - FunctionTool 如何从 Pydantic 模型 自动生成入参 Schema,以及如何让工具返回结构化数据

官方参考 ​

学习文档整合站点