第5课:给AI装工具 —— Tools 和 MCP
本课目标
前面几课的 Claude 只能"说话"。但做研究需要它能搜网页、写文件、跑代码。这节课教你怎么给 Claude"装手装脚"。
什么是 Tool(工具)?
打个比方:
- Claude 本身 = 一个超聪明的大脑
- Tool = 大脑的手脚和感官
没有工具的 Claude 只能靠自己的知识回答问题。有了工具,它可以:
| 工具名 | 能干嘛 | 研究平台里谁用 |
|---|---|---|
WebSearch |
搜索互联网 | 研究员 |
WebFetch |
打开网页读内容 | 研究员 |
Write |
创建/写入文件 | 研究员、分析师、报告员 |
Read |
读取文件内容 | 分析师、报告员 |
Glob |
搜索文件(按名字模式) | 分析师、报告员 |
Bash |
执行命令行命令 | 分析师(跑 Python 画图) |
Task |
派活给子 Agent | 组长 |
给 Claude 装上内置工具
"""
05_builtin_tools.py
让 Claude 使用内置工具
"""
import anyio
from claude_agent_sdk import (
ClaudeSDKClient, ClaudeAgentOptions,
AssistantMessage, TextBlock, ToolUseBlock
)
async def main():
options = ClaudeAgentOptions(
system_prompt="你是一个研究助手,会用工具搜索最新信息。",
allowed_tools=["WebSearch", "Read", "Write"], # 允许用这些工具
permission_mode="acceptEdits", # 自动批准文件编辑
max_turns=3,
)
async with ClaudeSDKClient(options=options) as client:
await client.query("搜索一下 2025 年全球AI芯片市场规模,把结果保存到 ai_chips.md")
async for msg in client.receive_response():
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"[文字] {block.text}")
elif isinstance(block, ToolUseBlock):
print(f"[工具调用] {block.name}({block.input})")
anyio.run(main)
关键参数:
allowed_tools— 白名单,只有列在这里的工具 Claude 才能用permission_mode— 权限模式:"default"— 每次用工具都要你手动批准(安全但麻烦)"acceptEdits"— 自动批准文件编辑(方便)"bypassPermissions"— 跳过所有权限检查(研究平台用这个)
运行后你会看到 Claude 主动去搜索网页,然后把结果写到文件里。这就是工具的力量。
观察工具调用过程
Claude 调用工具时,消息流是这样的:
1. AssistantMessage (ToolUseBlock: WebSearch "AI芯片市场规模 2025")
↑ Claude 决定搜索
2. UserMessage (ToolResultBlock: 搜索结果...)
↑ 工具执行完成,结果返回给 Claude
3. AssistantMessage (ToolUseBlock: Write "ai_chips.md")
↑ Claude 决定把结果写入文件
4. UserMessage (ToolResultBlock: 写入成功)
↑ 文件写入完成
5. AssistantMessage (TextBlock: "我已经搜索并保存了...")
↑ Claude 汇报完成
每一次工具调用就是一个"轮次"(turn),这就是为什么 max_turns 要设大一点,不然 Claude 用一两个工具就到上限了。
自定义工具(MCP Server)
内置工具不够用?你可以自己写工具给 Claude 用!
比如,做一个查询数据库的工具:
"""
05_custom_tool.py
自定义工具示例
"""
import anyio
from claude_agent_sdk import (
tool, create_sdk_mcp_server,
ClaudeSDKClient, ClaudeAgentOptions,
AssistantMessage, TextBlock
)
# 用 @tool 装饰器定义一个工具
@tool(
name="lookup_stock",
description="查询股票的最新价格和涨跌幅",
input_schema={"ticker": str} # 参数:股票代码
)
async def lookup_stock(args):
"""这里正常应该调真实API,这里用假数据演示"""
ticker = args["ticker"]
# 模拟数据库查询
fake_data = {
"TSLA": {"price": 248.50, "change": "+3.2%"},
"NVDA": {"price": 892.30, "change": "+1.8%"},
"AAPL": {"price": 195.60, "change": "-0.5%"},
}
data = fake_data.get(ticker.upper(), {"price": "未知", "change": "未知"})
return {
"content": [
{"type": "text", "text": f"{ticker}: 价格 ${data['price']},涨跌 {data['change']}"}
]
}
async def main():
# 创建 MCP 服务器,把工具注册进去
server = create_sdk_mcp_server(
name="stock-tools",
version="1.0.0",
tools=[lookup_stock]
)
options = ClaudeAgentOptions(
system_prompt="你是一个股票分析助手。用 lookup_stock 工具查询股价。",
mcp_servers={"stocks": server}, # 注册自定义工具
allowed_tools=["mcp__stocks__lookup_stock"], # 允许使用
max_turns=3,
)
async with ClaudeSDKClient(options=options) as client:
await client.query("帮我查一下特斯拉和英伟达的股价")
async for msg in client.receive_response():
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(block.text)
anyio.run(main)
自定义工具的套路:
- 用
@tool(name, description, input_schema)装饰一个 async 函数 - 函数接收
args字典,返回固定格式的结果 - 用
create_sdk_mcp_server()打包成服务 - 通过
mcp_servers参数注册到 client - 在
allowed_tools里用mcp__服务名__工具名格式允许
mcp__服务名__工具名 是怎么拼出来的?
回头看代码里的两个地方:
# 第一处:注册 MCP 服务器时的 key → 就是"服务名"
mcp_servers={"stocks": server}
# ^^^^^^
# 这个 key 就是服务名
# 第二处:@tool 装饰器里的 name → 就是"工具名"
@tool(name="lookup_stock", ...)
# ^^^^^^^^^^^^
# 这个 name 就是工具名
所以拼起来就是:mcp__stocks__lookup_stock
mcp __ stocks __ lookup_stock
↑ ↑ ↑
固定前缀 服务名 工具名
(mcp_servers (@tool 的
的 key) name 参数)
注意是双下划线 __,不是单下划线。
如果你有多个服务、每个服务有多个工具,命名就像这样:
mcp_servers={
"stocks": stock_server, # 股票服务
"weather": weather_server, # 天气服务
}
allowed_tools=[
"mcp__stocks__lookup_stock", # 股票服务 → 查股价工具
"mcp__stocks__get_history", # 股票服务 → 查历史工具
"mcp__weather__get_forecast", # 天气服务 → 查天气工具
]
这样设计的好处是:服务名起到命名空间的作用。即使两个服务都有叫 search 的工具,通过服务名也能区分开——mcp__stocks__search 和 mcp__weather__search 是两个不同的工具。
这就是 MCP(Model Context Protocol)的核心思想:给 AI 一个标准化的"接口"来调用外部功能。你的数据库、API、内部系统,都可以通过这种方式暴露给 Claude。
在研究平台里,工具是怎么分配的?
每个角色拿到的工具不一样,很像公司里不同岗位有不同的权限:
# 组长:只能派活,不能自己干活
lead_agent_tools = ["Task"]
# 研究员:搜索 + 写笔记
researcher_tools = ["WebSearch", "Write"]
# 数据分析师:读文件 + 跑代码 + 写结果
data_analyst_tools = ["Glob", "Read", "Bash", "Write"]
# 报告撰写人:读所有材料 + 跑代码生成PDF + 用技能指导
report_writer_tools = ["Skill", "Write", "Glob", "Read", "Bash"]
为什么要这样分?最小权限原则——每个角色只拿到它需要的工具,防止越权操作。研究员不需要跑代码,所以不给 Bash;组长不需要搜索,所以不给 WebSearch。
本课小结
- 工具让 Claude 从"只会说"变成"能动手"
allowed_tools控制能用哪些工具permission_mode控制是否需要人工审批- 自定义工具 通过
@tool+create_sdk_mcp_server()创建 - 不同角色分配不同工具,这是研究平台的设计哲学
课后练习
- 让 Claude 用
Bash工具执行ls命令,看看它能不能列出当前目录 - 写一个自定义工具
get_weather,返回假的天气数据,让 Claude 调用它 - 试试只给 Claude
Read工具(不给 Write),然后让它写文件,看看会怎样