Lesson 4: Tool Use 实战
学习目标
- 掌握 MCP Server 的开发流程
- 理解 Tool 定义的最佳实践
- 学会调试 MCP 工具调用
开发你的第一个 MCP Server
我们用一个真实案例来学习:构建一个天气查询 MCP Server,让 AI 能回答"今天北京天气怎么样"这类问题。
项目初始化
mkdir weather-mcp-server && cd weather-mcp-server
bun init
bun add @modelcontextprotocol/sdk zod定义 Server
// src/index.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
const server = new McpServer({
name: 'weather-server',
version: '1.0.0',
})
// Define a tool
server.tool(
'get_weather',
'Get current weather for a city',
{
city: z.string().describe('City name, e.g. Beijing, Tokyo'),
units: z.enum(['celsius', 'fahrenheit']).default('celsius')
.describe('Temperature units'),
},
async ({ city, units }) => {
// In production, call a real weather API
const response = await fetch(
`https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${city}`
)
const data = await response.json()
return {
content: [{
type: 'text',
text: JSON.stringify({
city,
temperature: units === 'celsius' ? data.current.temp_c : data.current.temp_f,
condition: data.current.condition.text,
humidity: data.current.humidity,
wind: data.current.wind_kph,
}, null, 2),
}],
}
}
)
// Start the server
const transport = new StdioServerTransport()
await server.connect(transport)Tool 定义的最佳实践
1. 清晰的名称和描述
AI 通过描述来决定是否使用这个工具。描述要准确说明工具做什么、什么时候用。
// Bad: 描述太模糊
server.tool('process', 'Process data', ...)
// Good: 描述清晰具体
server.tool(
'search_products',
'Search the product catalog by name, category, or price range. Returns matching products with price, availability, and ratings.',
...
)2. 严格的参数验证
使用 Zod 定义参数 schema,让无效输入在进入业务逻辑之前被拦截。
{
email: z.string().email().describe('Valid email address'),
amount: z.number().positive().max(10000).describe('Amount in USD, max $10,000'),
category: z.enum(['electronics', 'clothing', 'food']).describe('Product category'),
}3. 结构化的返回值
返回结构化数据(JSON),而不是自然语言。让 AI 自己决定怎么呈现给用户。
// Bad: 返回自然语言
return { content: [{ type: 'text', text: 'The weather in Beijing is sunny, 25°C' }] }
// Good: 返回结构化数据
return { content: [{ type: 'text', text: JSON.stringify({ city: 'Beijing', temp: 25, condition: 'sunny' }) }] }4. 错误处理
工具调用可能失败。返回有意义的错误信息,让 AI 能理解发生了什么并决定下一步。
try {
const result = await callExternalApi(params)
return { content: [{ type: 'text', text: JSON.stringify(result) }] }
} catch (error) {
return {
content: [{ type: 'text', text: JSON.stringify({
error: true,
message: `Failed to fetch data: ${error.message}`,
suggestion: 'Try again with a different city name',
}) }],
isError: true,
}
}在 Claude Desktop 中测试
配置 Claude Desktop 的 MCP 设置:
// ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"weather": {
"command": "bun",
"args": ["run", "/path/to/weather-mcp-server/src/index.ts"],
"env": {
"WEATHER_API_KEY": "your-api-key"
}
}
}
}重启 Claude Desktop,你就能直接问"北京天气怎么样",Claude 会自动调用你的 MCP Server。
调试技巧
使用 MCP Inspector
bunx @modelcontextprotocol/inspector bun src/index.tsInspector 提供 Web UI 来测试工具调用、查看参数和返回值。
日志输出
MCP Server 的 stdout 用于协议通信,所以日志必须输出到 stderr:
console.error('[DEBUG] Tool called with params:', params)常见问题
- Tool 没出现在 Claude 中: 检查 Server 启动是否成功,用 Inspector 验证
- 参数类型错误: Zod schema 可能太严格或不匹配 AI 的输出格式
- 超时: 外部 API 调用要设置合理的 timeout
- 权限问题: 确保 Server 进程有权限访问需要的资源
关键收获
- MCP Server 开发流程:定义 Tool → 实现逻辑 → 配置连接 → 测试
- 好的 Tool 定义 = 清晰描述 + 严格验证 + 结构化返回 + 错误处理
- 用 MCP Inspector 调试,日志输出到 stderr
- 从简单的单工具 Server 开始,逐步增加复杂度