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 权限

FunctionToolcheck_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 的返回值支持两种形态:单个 ToolChunkAsyncGenerator[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()适用场景
StatelessHttpMCPConfigHTTP MCP 服务,每次调用建临时会话
StatefulStdioMCPConfigSTDIO MCP 服务,需启动子进程
StatefulHttpMCPConfigHTTP MCP 服务,需保持长连接

关键规则:STDIO 必须是 Stateful。在 MCPClient 构造时,如果 mcp_configStdioMCPConfigis_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_nameMCPClient.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_toolsdisable_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 中决定

权限规则、路径匹配、PermissionContextPermissionMode 的完整说明见 权限拦截

常见错误

  • 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 配成 StatelessStdioMCPConfig 必须搭配 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,以及如何让工具返回结构化数据

官方参考

学习文档整合站点