Appearance
工具与 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"],
)| 参数 | 类型 | 职责 |
|---|---|---|
tools | list[ToolBase] | Python 工具对象(内置工具、FunctionTool、自定义 ToolBase) |
mcps | list[MCPClient] | MCP 客户端,工具列表由服务端动态发现 |
skills_or_loaders | `list[str | Skill |
tool_groups | list[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 的构造参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
func | Callable | 必填 | 被包装的 Python 函数 |
name | str | None | None | 工具名,默认用函数名 |
description | str | None | None | 工具描述,默认从 docstring 提取 |
is_concurrency_safe | bool | True | 是否可并发调用 |
is_read_only | bool | False | 是否只读(影响权限决策) |
is_state_injected | bool | False | 是否需要注入 Agent 状态(见下文) |
middlewares | list[ToolMiddlewareBase] | None | None | 工具级中间件 |
默认 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 的关键类属性:
| 属性 | 类型 | 说明 |
|---|---|---|
name | str | 暴露给 Agent 的工具名 |
description | str | Agent 可读的工具描述 |
input_schema | dict | 入参 JSON Schema |
is_concurrency_safe | bool | 是否可并发调用 |
is_read_only | bool | 是否只读,影响权限决策 |
is_state_injected | bool | 是否需要注入 Agent 状态(注入参数名 _agent_state) |
is_mcp | bool | 是否为 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)],
),
],
)运行时行为:
- basic 组始终激活。
tools/mcps/skills_or_loaders三个构造参数里的内容都进 basic 组。 - 非 basic 组默认未激活。Agent 初始看不到它们里面的工具。
- ResetTools 元工具自动注册。当 Toolkit 中存在至少一个非 basic 组时,Toolkit 自动注册一个名为
reset_tools的内置元工具。Agent 调用它可以激活或停用工具组:reset_tools(research=True, data_analysis=False)。 - "basic" 是保留名。不能在
tool_groups里传入name="basic"的组,否则会抛ValueError。
这让 Agent 可以按需"加载"工具集:先在 basic 组里完成初步分析,再通过 reset_tools 激活 research 组做深度搜索,完成后切回 basic 组释放上下文。
MCP 集成
AgentScope 2.x 通过 MCPClient 统一接入 MCP(Model Context Protocol)服务。MCP 工具不需要手写 Schema,由服务端动态发现。
两种连接模式
| 模式 | 配置类型 | 是否需要 connect() | 适用场景 |
|---|---|---|---|
| Stateless | HttpMCPConfig | 否 | HTTP MCP 服务,每次调用建临时会话 |
| Stateful | StdioMCPConfig | 是 | STDIO MCP 服务,需启动子进程 |
| Stateful | HttpMCPConfig | 是 | 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 字段一览
| 字段 | 类型 | 说明 |
|---|---|---|
name | str | MCP 服务名,用于工具命名空间 |
is_stateful | bool | 是否需要显式 connect() / close() |
mcp_config | StdioMCPConfig | HttpMCPConfig | MCP 服务配置(按 type 字段判别) |
enable_tools | list[str] | None | 白名单,只暴露指定工具 |
disable_tools | list[str] | None | 黑名单,过滤指定工具 |
execution_timeout | float | 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不同工具的默认权限策略:
| 工具来源 | 默认权限行为 |
|---|---|
FunctionTool | ASK(每次调用需用户确认) |
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,以及如何让工具返回结构化数据