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.ts

Inspector 提供 Web UI 来测试工具调用、查看参数和返回值。

日志输出

MCP Server 的 stdout 用于协议通信,所以日志必须输出到 stderr:

console.error('[DEBUG] Tool called with params:', params)

常见问题

  1. Tool 没出现在 Claude 中: 检查 Server 启动是否成功,用 Inspector 验证
  2. 参数类型错误: Zod schema 可能太严格或不匹配 AI 的输出格式
  3. 超时: 外部 API 调用要设置合理的 timeout
  4. 权限问题: 确保 Server 进程有权限访问需要的资源

关键收获

  1. MCP Server 开发流程:定义 Tool → 实现逻辑 → 配置连接 → 测试
  2. 好的 Tool 定义 = 清晰描述 + 严格验证 + 结构化返回 + 错误处理
  3. 用 MCP Inspector 调试,日志输出到 stderr
  4. 从简单的单工具 Server 开始,逐步增加复杂度