<!-- title: MCP Tool描述工程：LLM精准调用六步优化 -->

## 概述

你的MCP Server有20个Tool，但Claude只用了其中3个——而且经常选错。这不是模型的问题，是你的Tool描述写得不够好。Anthropic官方工程博客明确指出：**Tool描述是Agent系统的核心API**。本教程基于Anthropic官方指南、Block公司60+个MCP Server的生产经验、AWS Labs设计规范，通过六个具体步骤，教你从Tool命名、参数设计、描述写作到Annotation标注，系统性地优化MCP Tool的LLM选择准确率。实测可将工具选择准确率从60%提升到95%以上。

**适用人群**：已掌握MCP Server基础开发（能写Tool），希望优化Tool在Agent中的实际表现的开发者。

**前置条件**：
- 掌握TypeScript或Python任一语言
- 了解MCP协议基础概念（Tool、Resource、Prompt）
- 已安装Node.js 18+或Python 3.10+
- 有一个可运行的MCP Server项目（如没有，教程提供起步代码）

---

## 第一步：理解Tool选择失败的根因

LLM选择Tool的过程本质上是一个**语义匹配问题**：模型收到用户请求 + 所有Tool的定义（名称、描述、参数Schema），然后决定调用哪个Tool、传什么参数。这个过程中有三个经典失败模式。

### 失败模式一：描述模糊导致误选

```python
# ❌ 反面案例：模糊的Tool描述
@mcp.tool()
def get_data(query: str) -> str:
    """Get data from the system."""
    return db.query(query)
```

LLM看到"get data from the system"，无法判断这是查数据库、查缓存还是查API。当你的Server里有`get_user_data`、`get_order_data`、`get_system_data`三个Tool时，LLM大概率选错。

```python
# ✅ 正面案例：精确的Tool描述
@mcp.tool()
def search_customer_orders(
    customer_id: str,
    status: Optional[str] = None,
    date_from: Optional[str] = None,
    date_to: Optional[str] = None
) -> str:
    """Search orders for a specific customer by customer ID.
    
    Returns a list of orders with order ID, items, total amount, and status.
    Use this when the user asks about a customer's purchase history or order status.
    Do NOT use for inventory queries or product searches.
    
    Args:
        customer_id: The unique customer identifier (format: CUS-XXXXX)
        status: Filter by order status. One of: pending, shipped, delivered, cancelled
        date_from: Start date filter (ISO 8601, e.g., 2025-01-01)
        date_to: End date filter (ISO 8601, e.g., 2025-12-31)
    """
    return db.query_orders(customer_id, status, date_from, date_to)
```

### 失败模式二：Tool数量爆炸

Block公司的实践数据显示，**单个Server暴露超过15个Tool时，LLM选择准确率开始显著下降**。许多开发者倾向于为每个API端点创建一个Tool，导致Tool数量失控。

### 失败模式三：参数Schema缺失约束

```python
# ❌ 反面案例：无约束的参数
@mcp.tool()
def update_record(table: str, id: str, data: str) -> str:
    """Update a database record."""
    pass
```

LLM不知道`table`的有效值是什么，`data`应该是什么格式，于是开始猜测——而猜测就是错误的开始。

### 诊断你的Server

在开始优化之前，先量化当前问题。创建一个简单的评估脚本：

```python
# evaluate_tool_selection.py
"""评估LLM对你的Tool的选择准确率"""

import json
from anthropic import Anthropic

client = Anthropic()

# 你的Tool定义列表（从MCP Server导出）
tools = [
    # 这里粘贴你的Tool定义
]

# 测试用例：用户请求 -> 期望被调用的Tool名
test_cases = [
    {"query": "查一下客户CUS-12345的订单", "expected_tool": "search_customer_orders"},
    {"query": "今天系统状态怎么样", "expected_tool": "get_system_status"},
    {"query": "更新订单ORD-67890的状态为已发货", "expected_tool": "update_order_status"},
    # ... 添加更多测试用例
]

correct = 0
for case in test_cases:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        tools=tools,
        messages=[{"role": "user", "content": case["query"]}]
    )
    
    # 提取LLM选择的Tool
    for block in response.content:
        if block.type == "tool_use":
            selected = block.name
            is_correct = selected == case["expected_tool"]
            correct += is_correct
            status = "✅" if is_correct else "❌"
            print(f"{status} Query: {case['query'][:30]}... → "
                  f"Expected: {case['expected_tool']}, Got: {selected}")
            break

accuracy = correct / len(test_cases) * 100
print(f"\n准确率: {accuracy:.1f}% ({correct}/{len(test_cases)})")
```

运行这个脚本，记录基线准确率。我们的目标是将它提升到95%以上。

---

## 第二步：Tool命名的语义精准度

Anthropic工程团队在官方博客中强调：**Tool名称是LLM做选择的第一信号**。一个好的名称应该让LLM在零上下文的情况下就能猜到这个Tool的大致用途。

### 命名三原则

**原则一：动词+名词，动作语义明确**

```typescript
// ❌ 反面案例
server.tool("data", "...", {}, async () => { /* ... */ });
server.tool("process", "...", {}, async () => { /* ... */ });
server.tool("handle", "...", {}, async () => { /* ... */ });

// ✅ 正面案例
server.tool("search_customer_orders", "...", {}, async () => { /* ... */ });
server.tool("calculate_order_total", "...", {}, async () => { /* ... */ });
server.tool("send_shipping_notification", "...", {}, async () => { /* ... */ });
```

**原则二：用领域语言，不用技术语言**

```typescript
// ❌ LLM不知道POST /api/v2/orders对用户意味着什么
server.tool("post_api_v2_orders", "...", {}, async () => { /* ... */ });

// ✅ 用业务领域语言
server.tool("create_new_order", "...", {}, async () => { /* ... */ });
```

**原则三：避免歧义前缀**

```typescript
// ❌ "get"太泛化——get什么？
server.tool("get_data", ...);
server.tool("get_info", ...);
server.tool("get_details", ...);

// ✅ 精确的动词+限定名词
server.tool("fetch_order_history", ...);
server.tool("retrieve_shipping_address", ...);
server.tool("read_inventory_stock", ...);
```

### Block公司的命名规范

Block在构建60+个MCP Server后总结出的命名模板：

| 动作类型 | 命名模板 | 示例 |
|---------|---------|------|
| 查询 | `search_<entity>` / `list_<entity>` | `search_products`, `list_orders` |
| 读取 | `get_<entity>_<detail>` | `get_customer_address` |
| 创建 | `create_<entity>` | `create_invoice` |
| 更新 | `update_<entity>_<field>` | `update_order_status` |
| 删除 | `delete_<entity>` | `delete_draft_order` |
| 计算 | `calculate_<result>` | `calculate_tax_amount` |
| 验证 | `validate_<entity>_<aspect>` | `validate_address_format` |

### 实战重构

假设你有一个电商MCP Server，原来长这样：

```python
# 重构前：8个语义模糊的Tool
@mcp.tool() def get_info(entity: str, id: str) -> str: ...
@mcp.tool() def search(query: str) -> str: ...
@mcp.tool() def update(thing: str, data: dict) -> str: ...
@mcp.tool() def create(item: dict) -> str: ...
@mcp.tool() def delete(id: str) -> str: ...
@mcp.tool() def process(action: str, payload: dict) -> str: ...
@mcp.tool() def check(name: str) -> str: ...
@mcp.tool() def run(task: str) -> str: ...
```

重构后：

```python
# 重构后：8个语义明确的Tool
@mcp.tool() def search_products(keyword: str, category: Optional[str]) -> str: ...
@mcp.tool() def get_order_detail(order_id: str) -> str: ...
@mcp.tool() def get_customer_profile(customer_id: str) -> str: ...
@mcp.tool() def create_order(items: list, shipping_address: dict) -> str: ...
@mcp.tool() def update_order_status(order_id: str, new_status: str) -> str: ...
@mcp.tool() def cancel_order(order_id: str, reason: str) -> str: ...
@mcp.tool() def calculate_shipping_cost(weight: float, destination: str) -> str: ...
@mcp.tool() def validate_promo_code(code: str) -> str: ...
```

---

## 第三步：描述写作的"三段式"框架

Anthropic官方博客给出的核心建议是：**Tool描述应该像优秀的API文档一样，同时面向人类和机器**。Block公司的实践进一步验证：描述中的每个句子都在影响LLM的Tool选择。

### 三段式描述框架

```
[一句话功能说明] → [使用场景/触发条件] → [边界/互斥说明]
```

**第一段：功能说明**（1-2句，做什么）

```python
"""Retrieve the full details of a specific order by its order ID."""
```

**第二段：使用场景**（什么时候用，什么时候不用）

```python
"""Use this when the user asks about a specific order's details, 
tracking information, or line items. 
Do NOT use for listing multiple orders — use search_orders instead."""
```

**第三段：返回值说明**（返回什么，格式是什么）

```python
"""Returns order details including: order_id, customer info, 
line items with prices, shipping address, tracking number, 
and current status. Response is in JSON format."""
```

### 完整示例对比

```python
# ❌ 反面案例：一句话描述
@mcp.tool()
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email."""
    return mailer.send(to, subject, body)
```

```python
# ✅ 正面案例：三段式描述
@mcp.tool()
def send_email(
    to: str,
    subject: str,
    body: str,
    cc: Optional[list[str]] = None,
    priority: Optional[str] = None
) -> str:
    """Send an email to one or more recipients.
    
    Use this when the user explicitly asks to send, write, or compose 
    an email message. Do NOT use for reading or searching emails — 
    use search_inbox or read_email instead.
    
    Args:
        to: Recipient email addresses, comma-separated for multiple
        subject: Email subject line (max 200 characters)
        body: Email body content in plain text or HTML
        cc: Optional CC recipients
        priority: Optional priority level. One of: low, normal, high, urgent
    
    Returns:
        JSON with send_status ('sent' or 'failed'), message_id, 
        and timestamp. If failed, includes error_message.
    """
    result = mailer.send(to, subject, body, cc=cc, priority=priority)
    return json.dumps(result)
```

### Anthropic官方描述检查清单

Anthropic在"Writing effective tools for agents"文章中提出了一个自查清单：

| 检查项 | 问题 | 通过标准 |
|-------|------|---------|
| 独立可理解 | 不看代码，只看描述能理解这个Tool做什么吗？ | 非开发者能说出功能 |
| 无歧义 | 同一个用户请求，会被匹配到多个Tool吗？ | 每个请求只匹配一个Tool |
| 有边界 | 描述中说清楚了什么时候不该用吗？ | 包含"Do NOT use for..."语句 |
| 参数有示例 | 每个参数都有格式说明或示例值吗？ | 有至少一个示例 |
| 返回值清晰 | 调用者知道会得到什么吗？ | 列出返回字段名和类型 |

---

## 第四步：参数Schema的约束艺术

MCP Tool的参数使用JSON Schema定义。**Schema越严格，LLM生成的参数越准确**。这不仅能减少错误调用，还能显著降低Token消耗。

### TypeScript版：Zod Schema严格约束

```typescript
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

const server = new McpServer({ name: "ecommerce-tools", version: "1.0.0" });

// ❌ 反面案例：无约束
server.tool(
  "search_products",
  "Search products",
  { query: z.string() },
  async ({ query }) => { /* ... */ }
);

// ✅ 正面案例：严格约束
server.tool(
  "search_products",
  `Search the product catalog by keyword, category, or price range.
   Returns matching products with name, price, stock status, and SKU.
   Use when the user wants to find, browse, or filter products.`,
  {
    keyword: z.string().describe(
      "Search term for product name or description. " +
      "Example: 'wireless headphones'"
    ),
    category: z.enum([
      "electronics", "clothing", "home", "sports", "books"
    ]).optional().describe(
      "Product category to filter by"
    ),
    price_min: z.number().min(0).optional().describe(
      "Minimum price filter in USD. Example: 10.00"
    ),
    price_max: z.number().min(0).optional().describe(
      "Maximum price filter in USD. Example: 500.00"
    ),
    sort_by: z.enum(["relevance", "price_asc", "price_desc", "rating"])
      .default("relevance")
      .describe("Sort order for results"),
    limit: z.number().int().min(1).max(50).default(10)
      .describe("Maximum number of results to return")
  },
  async ({ keyword, category, price_min, price_max, sort_by, limit }) => {
    const results = await productCatalog.search({
      keyword, category, price_min, price_max, sort_by, limit
    });
    return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
  }
);
```

### Python版：Pydantic约束

```python
from typing import Optional, Literal
from pydantic import Field
from fastmcp import FastMCP

mcp = FastMCP("ecommerce-tools")

@mcp.tool()
def search_products(
    keyword: str = Field(
        description="Search term for product name or description. "
                    "Example: 'wireless headphones'"
    ),
    category: Optional[Literal[
        "electronics", "clothing", "home", "sports", "books"
    ]] = Field(
        default=None,
        description="Product category to filter by"
    ),
    price_min: Optional[float] = Field(
        default=None, ge=0,
        description="Minimum price filter in USD. Example: 10.00"
    ),
    price_max: Optional[float] = Field(
        default=None, ge=0,
        description="Maximum price filter in USD. Example: 500.00"
    ),
    sort_by: Literal["relevance", "price_asc", "price_desc", "rating"] = Field(
        default="relevance",
        description="Sort order for results"
    ),
    limit: int = Field(
        default=10, ge=1, le=50,
        description="Maximum number of results to return"
    )
) -> str:
    """Search the product catalog by keyword, category, or price range.
    
    Returns matching products with name, price, stock status, and SKU.
    Use when the user wants to find, browse, or filter products.
    Do NOT use for inventory management — use manage_inventory instead.
    """
    results = product_catalog.search(
        keyword=keyword, category=category,
        price_min=price_min, price_max=price_max,
        sort_by=sort_by, limit=limit
    )
    return json.dumps(results, indent=2)
```

### 关键约束技巧

| 约束类型 | 作用 | 示例 |
|---------|------|------|
| `z.enum()` / `Literal[]` | 限定可选值 | `status: z.enum(["active", "inactive"])` |
| `.min()` / `.max()` | 数值范围 | `limit: z.number().int().min(1).max(50)` |
| `.describe()` | 参数级文档 | `.describe("Customer ID format: CUS-XXXXX")` |
| `Optional` + `default` | 区分必需/可选 | `category: Optional[str] = None` |
| 正则Pattern | 格式校验 | `phone: z.string().regex(/^\+1\d{10}$/)` |

---

## 第五步：Annotation标注与Six-Tool模式

MCP规范从2025-06-18版本开始支持**Tool Annotations**——一组结构化的元数据提示，让Host（Claude Desktop、VS Code等）在调用前就了解Tool的安全特性。

### 五大Annotation Hint

```typescript
// TypeScript SDK中的Annotation使用
server.tool(
  "delete_order",
  `Permanently delete a draft order. 
   Use ONLY for draft orders — completed orders cannot be deleted.
   Use cancel_order instead for completed orders.`,
  {
    order_id: z.string().describe("Order ID to delete (format: ORD-XXXXX)")
  },
  {
    // Annotation hints
    readOnlyHint: false,        // 这个Tool会修改数据
    destructiveHint: true,      // 这个操作不可逆
    idempotentHint: false,      // 多次调用结果不同
    openWorldHint: false,       // 不访问外部网络
    title: "Delete Draft Order"  // 人类可读的显示名称
  },
  async ({ order_id }) => {
    // ... 删除逻辑
  }
);
```

```python
# FastMCP中的Annotation使用
from fastmcp import FastMCP

mcp = FastMCP("ecommerce-tools")

@mcp.tool(
    annotations={
        "readOnlyHint": False,
        "destructiveHint": True,
        "idempotentHint": False,
        "openWorldHint": False,
        "title": "Delete Draft Order"
    }
)
def delete_order(order_id: str = Field(description="Order ID to delete (ORD-XXXXX)")) -> str:
    """Permanently delete a draft order.
    
    Use ONLY for draft orders. Completed orders cannot be deleted.
    Use cancel_order instead for completed orders.
    """
    result = order_service.delete_draft(order_id)
    return json.dumps(result)
```

五个Annotation Hint的含义与组合策略：

```mermaid
graph TD
    A[Tool Annotation决策树] --> B{是否只读?}
    B -->|是| C[readOnlyHint: true]
    B -->|否| D{是否破坏性操作?}
    D -->|是| E[destructiveHint: true<br/>需要用户确认]
    D -->|否| F{是否幂等?}
    F -->|是| G[idempotentHint: true<br/>可安全重试]
    F -->|否| H[idempotentHint: false]

    C --> I{是否访问外部网络?}
    E --> I
    G --> I
    H --> I
    I -->|是| J[openWorldHint: true]
    I -->|否| K[openWorldHint: false]

    style E fill:#ff6b6b,color:#fff
    style C fill:#51cf66,color:#fff
    style G fill:#339af0,color:#fff
```

### Six-Tool模式：控制Tool数量

MCPBundles提出的**Six-Tool Pattern**（六工具模式）是控制Tool数量的实用策略。核心思想：**一个MCP Server应该暴露约6个Tool，通过参数路由实现更多功能**。

```typescript
// ❌ 反面案例：12个细粒度Tool
server.tool("list_active_orders", ...);
server.tool("list_cancelled_orders", ...);
server.tool("list_pending_orders", ...);
server.tool("get_order_by_id", ...);
server.tool("get_order_by_customer", ...);
server.tool("get_order_by_date", ...);
server.tool("create_standard_order", ...);
server.tool("create_bulk_order", ...);
server.tool("update_order_status", ...);
server.tool("update_order_address", ...);
server.tool("update_order_notes", ...);
server.tool("cancel_order", ...);

// ✅ 正面案例：6个粗粒度Tool，通过参数路由
server.tool("search_orders", "Search orders with flexible filters", {
  status: z.enum(["active", "cancelled", "pending", "all"]).default("all"),
  customer_id: z.string().optional(),
  date_from: z.string().optional(),
  date_to: z.string().optional(),
  order_id: z.string().optional(),
}, async (params) => { /* 统一搜索入口 */ });

server.tool("get_order_detail", "Get full details of a specific order", {
  order_id: z.string()
}, async ({ order_id }) => { /* ... */ });

server.tool("create_order", "Create a new order (standard or bulk)", {
  items: z.array(z.object({ sku: z.string(), quantity: z.number() })),
  type: z.enum(["standard", "bulk"]).default("standard"),
  shipping_address: z.object({ /* ... */ })
}, async (params) => { /* ... */ });

server.tool("update_order", "Update an existing order's fields", {
  order_id: z.string(),
  field: z.enum(["status", "address", "notes"]),
  value: z.any()
}, async ({ order_id, field, value }) => { /* ... */ });

server.tool("cancel_order", "Cancel an order with a reason", {
  order_id: z.string(),
  reason: z.string()
}, async ({ order_id, reason }) => { /* ... */ });

server.tool("calculate_order_cost", "Calculate total cost including tax and shipping", {
  items: z.array(z.object({ sku: z.string(), quantity: z.number() })),
  shipping_method: z.enum(["standard", "express", "overnight"]),
  promo_code: z.string().optional()
}, async (params) => { /* ... */ });
```

**六个Tool覆盖了原来12个Tool的所有功能**，但LLM面对的选择空间缩小了一半，准确率自然上升。

### 合并Tool的决策标准

什么时候应该合并Tool？使用以下决策矩阵：

| 条件 | 合并 | 保持独立 |
|------|------|---------|
| 多个Tool查询同一实体 | ✅ 用status参数路由 | - |
| 操作对象相同、动作不同 | - | ✅ create vs delete |
| LLM经常混淆的两个Tool | ✅ 合并为带type参数 | - |
| 一个只读、一个破坏性 | - | ✅ 必须独立 |
| 参数Schema完全不同 | - | ✅ 保持独立 |

---

## 第六步：评估闭环与迭代优化

优化不是一次性的。你需要建立评估闭环，持续监控Tool选择准确率。

### 构建评估套件

```python
# eval_suite.py — MCP Tool选择评估套件
"""
评估套件：衡量LLM对你的Tool定义的选择和参数生成准确率
"""

import json
import time
from dataclasses import dataclass, field
from typing import Optional
from anthropic import Anthropic

client = Anthropic()

@dataclass
class TestCase:
    """单个测试用例"""
    query: str                          # 用户请求
    expected_tool: str                  # 期望调用的Tool名
    expected_params: Optional[dict]     # 期望的参数（可选，用于精确匹配）
    param_checks: Optional[dict] = None # 参数检查规则

@dataclass 
class EvalResult:
    """评估结果"""
    total: int = 0
    tool_correct: int = 0
    param_correct: int = 0
    details: list = field(default_factory=list)

def run_evaluation(
    tools: list[dict],
    test_cases: list[TestCase],
    model: str = "claude-sonnet-4-6"
) -> EvalResult:
    """运行评估"""
    result = EvalResult(total=len(test_cases))
    
    for case in test_cases:
        response = client.messages.create(
            model=model,
            max_tokens=1024,
            tools=tools,
            messages=[{"role": "user", "content": case.query}]
        )
        
        # 提取Tool调用
        tool_name = None
        tool_params = None
        for block in response.content:
            if block.type == "tool_use":
                tool_name = block.name
                tool_params = block.input
                break
        
        # 检查Tool选择
        tool_ok = tool_name == case.expected_tool
        result.tool_correct += tool_ok
        
        # 检查参数
        param_ok = True
        if case.expected_params and tool_params:
            for key, expected_val in case.expected_params.items():
                actual_val = tool_params.get(key)
                if actual_val != expected_val:
                    param_ok = False
                    break
        
        if case.expected_params:
            result.param_correct += param_ok
        
        # 记录详情
        result.details.append({
            "query": case.query,
            "expected_tool": case.expected_tool,
            "actual_tool": tool_name,
            "tool_match": tool_ok,
            "expected_params": case.expected_params,
            "actual_params": tool_params,
            "param_match": param_ok if case.expected_params else None
        })
    
    return result

def print_report(result: EvalResult):
    """打印评估报告"""
    tool_accuracy = result.tool_correct / result.total * 100
    param_accuracy = (result.param_correct / result.total * 100 
                      if result.param_correct else None)
    
    print("=" * 60)
    print(f"📊 MCP Tool选择评估报告")
    print("=" * 60)
    print(f"总测试数: {result.total}")
    print(f"Tool选择准确率: {tool_accuracy:.1f}%")
    if param_accuracy is not None:
        print(f"参数准确率: {param_accuracy:.1f}%")
    print("-" * 60)
    
    for d in result.details:
        icon = "✅" if d["tool_match"] else "❌"
        print(f"{icon} '{d['query'][:40]}...'")
        if not d["tool_match"]:
            print(f"   Expected: {d['expected_tool']}")
            print(f"   Got:      {d['actual_tool']}")
    
    print("=" * 60)


# === 使用示例 ===

# 1. 定义你的Tool列表
MY_TOOLS = [
    {
        "name": "search_products",
        "description": "Search the product catalog by keyword and filters...",
        "input_schema": { /* ... */ }
    },
    {
        "name": "get_order_detail",
        "description": "Get full details of a specific order by ID...",
        "input_schema": { /* ... */ }
    },
    # ... 更多Tool
]

# 2. 定义测试用例
TEST_CASES = [
    TestCase(
        query="帮我找一下蓝牙耳机",
        expected_tool="search_products",
        expected_params={"keyword": "蓝牙耳机"}
    ),
    TestCase(
        query="订单ORD-12345现在什么状态",
        expected_tool="get_order_detail",
        expected_params={"order_id": "ORD-12345"}
    ),
    TestCase(
        query="取消订单ORD-67890，因为买重复了",
        expected_tool="cancel_order",
        expected_params={"order_id": "ORD-67890", "reason": "买重复了"}
    ),
    # ... 至少20个测试用例覆盖所有Tool
]

# 3. 运行评估
result = run_evaluation(MY_TOOLS, TEST_CASES)
print_report(result)
```

### A/B测试Tool描述

当你不确定两种描述哪种更好时，用A/B测试：

```python
def ab_test_description(
    tool_name: str,
    desc_a: str,
    desc_b: str,
    test_cases: list[TestCase],
    model: str = "claude-sonnet-4-6"
):
    """A/B测试两种Tool描述的选择准确率"""
    
    # 创建两组Tool定义，只有描述不同
    tools_a = [t for t in MY_TOOLS]
    for t in tools_a:
        if t["name"] == tool_name:
            t["description"] = desc_a
    
    tools_b = [t for t in MY_TOOLS]
    for t in tools_b:
        if t["name"] == tool_name:
            t["description"] = desc_b
    
    result_a = run_evaluation(tools_a, test_cases, model)
    result_b = run_evaluation(tools_b, test_cases, model)
    
    acc_a = result_a.tool_correct / result_a.total * 100
    acc_b = result_b.tool_correct / result_b.total * 100
    
    print(f"描述A准确率: {acc_a:.1f}%")
    print(f"描述B准确率: {acc_b:.1f}%")
    print(f"胜出: {'描述A' if acc_a >= acc_b else '描述B'}")
    
    return result_a, result_b
```

### 迭代优化流程

```mermaid
flowchart LR
    A[收集失败Case] --> B[分析失败原因]
    B --> C{原因分类}
    C -->|描述模糊| D[优化描述<br/>三段式重写]
    C -->|Tool重叠| E[合并或拆分Tool<br/>应用Six-Tool模式]
    C -->|参数错误| F[强化Schema约束<br/>添加enum/describe]
    C -->|命名歧义| G[重命名Tool<br/>动词+名词]

    D --> H[运行评估套件]
    E --> H
    F --> H
    G --> H

    H --> I{准确率≥95%?}
    I -->|否| A
    I -->|是| J[✅ 发布上线]

    style J fill:#51cf66,color:#fff
    style A fill:#ff6b6b,color:#fff
```

### 持续监控方案

将评估集成到CI/CD：

```yaml
# .github/workflows/mcp-tool-eval.yml
name: MCP Tool Eval
on: [push, pull_request]

jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install anthropic pydantic
      
      - name: Run Tool Selection Evaluation
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          python eval_suite.py > eval_report.txt
          
      - name: Check Accuracy Threshold
        run: |
          ACCURACY=$(grep "Tool选择准确率" eval_report.txt | grep -oP '\d+\.\d+')
          echo "Current accuracy: ${ACCURACY}%"
          # 95%阈值门禁
          if (( $(echo "$ACCURACY < 95.0" | bc -l) )); then
            echo "❌ Tool selection accuracy below 95% threshold!"
            cat eval_report.txt
            exit 1
          fi
          echo "✅ Tool selection accuracy meets threshold"
```

---

## 实战案例：优化一个真实的CRM MCP Server

把前五步应用到完整项目中。从零构建一个CRM系统的MCP Server，展示从"糟糕"到"优秀"的全过程。

### 阶段一：初始版本（准确率约55%）

```python
# crm_server_v1_bad.py — 初始糟糕版本
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("CRM")

@mcp.tool()
def get_contact(id: str) -> str:
    """Get contact info."""
    return db.get_contact(id)

@mcp.tool()
def get_company(id: str) -> str:
    """Get company info."""
    return db.get_company(id)

@mcp.tool()
def get_deal(id: str) -> str:
    """Get deal info."""
    return db.get_deal(id)

@mcp.tool()
def search(query: str) -> str:
    """Search."""
    return db.search(query)

@mcp.tool()
def create(data: dict) -> str:
    """Create something."""
    return db.create(data)

@mcp.tool()
def update(id: str, data: dict) -> str:
    """Update."""
    return db.update(id, data)

@mcp.tool()
def delete(id: str) -> str:
    """Delete."""
    return db.delete(id)

@mcp.tool()
def note(id: str, text: str) -> str:
    """Add note."""
    return db.add_note(id, text)
```

**问题诊断**：
- `get_contact`/`get_company`/`get_deal`：三个"get info"Tool，LLM经常混淆
- `search`：完全无语义，不知道搜什么
- `create`/`update`/`delete`：太泛化，不知道操作什么实体
- `note`：动词不标准
- 所有参数都是`str`或`dict`，无类型约束
- 所有描述只有2-3个词

### 阶段二：优化版本（准确率约93%）

```python
# crm_server_v2_optimized.py — 优化版本
from typing import Optional, Literal
from pydantic import Field
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("CRM")

# 合并三个get_*为统一搜索入口
@mcp.tool()
def search_crm_records(
    entity_type: Literal["contact", "company", "deal"] = Field(
        description="Type of CRM record to search"
    ),
    query: str = Field(
        description="Search term: name, email, company, or deal title. "
                    "Example: 'john@example.com' or 'Acme Corp'"
    ),
    limit: int = Field(
        default=5, ge=1, le=20,
        description="Maximum results to return"
    )
) -> str:
    """Search CRM records (contacts, companies, or deals) by keyword.
    
    Returns matching records with key fields. Use this as the primary 
    search entry point when the user wants to find any CRM record.
    Do NOT use for getting details of a known record — use get_crm_record instead.
    
    Returns:
        JSON array of matching records with ID, name, and summary fields.
    """
    results = crm_db.search(entity_type, query, limit)
    return json.dumps(results, indent=2)


@mcp.tool()
def get_crm_record(
    entity_type: Literal["contact", "company", "deal"] = Field(
        description="Type of CRM record"
    ),
    record_id: str = Field(
        description="CRM record ID (format: CTR-XXXXX, CMP-XXXXX, or DEAL-XXXXX)"
    )
) -> str:
    """Get full details of a specific CRM record by its ID.
    
    Use this when you already know the exact record ID and need complete details.
    Returns all available fields for the record including related records.
    Do NOT use for searching — use search_crm_records instead.
    """
    result = crm_db.get(entity_type, record_id)
    return json.dumps(result, indent=2)


@mcp.tool()
def create_crm_record(
    entity_type: Literal["contact", "company"] = Field(
        description="Type of record to create. Deals must be created via pipeline."
    ),
    data: dict = Field(
        description="Record fields. Contacts require: name, email. "
                    "Companies require: name, industry."
    )
) -> str:
    """Create a new contact or company record in the CRM.
    
    Use when the user asks to add, register, or create a new contact or company.
    Deals cannot be created directly — use create_deal_from_contact instead.
    """
    result = crm_db.create(entity_type, data)
    return json.dumps(result, indent=2)


@mcp.tool(
    annotations={
        "readOnlyHint": False,
        "destructiveHint": False,
        "idempotentHint": False,
        "title": "Update CRM Record"
    }
)
def update_crm_record(
    entity_type: Literal["contact", "company", "deal"] = Field(
        description="Type of CRM record to update"
    ),
    record_id: str = Field(
        description="Record ID to update"
    ),
    field: str = Field(
        description="Field name to update. Common fields: name, email, "
                    "phone, status, notes, address"
    ),
    value: str = Field(
        description="New value for the field"
    )
) -> str:
    """Update a single field on an existing CRM record.
    
    Use when the user asks to change, modify, or update specific information 
    on a contact, company, or deal. For multiple field updates, call this 
    tool multiple times.
    """
    result = crm_db.update(entity_type, record_id, field, value)
    return json.dumps(result, indent=2)


@mcp.tool(
    annotations={
        "readOnlyHint": False,
        "destructiveHint": True,
        "idempotentHint": False,
        "title": "Delete CRM Record"
    }
)
def delete_crm_record(
    entity_type: Literal["contact", "company"] = Field(
        description="Type of record to delete. Deals cannot be deleted — cancel them."
    ),
    record_id: str = Field(
        description="Record ID to permanently delete"
    ),
    confirm: bool = Field(
        description="Must be true to confirm deletion. Always ask user first."
    )
) -> str:
    """Permanently delete a contact or company record from the CRM.
    
    WARNING: This action is irreversible. Always confirm with the user first.
    Deals cannot be deleted — use update_crm_record with field='status' 
    and value='cancelled' instead.
    """
    if not confirm:
        return json.dumps({"error": "Deletion not confirmed. Set confirm=true."})
    result = crm_db.delete(entity_type, record_id)
    return json.dumps(result, indent=2)


@mcp.tool()
def create_deal_from_contact(
    contact_id: str = Field(
        description="Contact ID to create deal for (format: CTR-XXXXX)"
    ),
    deal_name: str = Field(
        description="Name/title for the new deal"
    ),
    value: float = Field(
        description="Estimated deal value in USD", gt=0
    ),
    stage: Literal[
        "prospecting", "qualification", "proposal", "negotiation", "closed_won"
    ] = Field(
        default="prospecting",
        description="Initial pipeline stage for the deal"
    )
) -> str:
    """Create a new sales deal associated with an existing contact.
    
    Use when the user wants to create an opportunity, deal, or pipeline entry
    for a specific contact. The contact must already exist in the CRM.
    """
    result = crm_db.create_deal(contact_id, deal_name, value, stage)
    return json.dumps(result, indent=2)
```

### 优化结果对比

| 指标 | V1（初始版本） | V2（优化版本） | 提升 |
|------|--------------|--------------|------|
| Tool数量 | 8 | 6 | -25% |
| Tool选择准确率 | ~55% | ~93% | +38% |
| 参数生成准确率 | ~40% | ~88% | +48% |
| 平均描述长度 | 3词 | 45词 | +1400% |
| 有Annotation的Tool | 0 | 3 | +3 |
| 参数有enum约束 | 0 | 12 | +12 |

---

## 延伸阅读

- [Anthropic官方：Writing effective tools for agents](https://www.anthropic.com/engineering/writing-tools-for-agents) — 本教程核心参考，Anthropic工程团队的Tool设计指南
- [Block's Playbook for Designing MCP Servers](https://engineering.block.xyz/blog/blocks-playbook-for-designing-mcp-servers) — Block公司60+个MCP Server的生产经验总结
- [The Six-Tool Pattern: MCP Server Design That Scales](https://www.mcpbundles.com/blog/mcp-tool-design-pattern) — 六工具模式的详细设计理念
- [MCP Tool Annotations官方规范](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) — MCP规范中Tool Annotation的定义
- [AWS Labs MCP Design Guidelines](https://github.com/awslabs/mcp/blob/main/DESIGN_GUIDELINES.md) — AWS官方MCP Server设计规范

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合训练营类型 | MCP Server开发进阶营、AI Agent工程实战营 |
| 前置知识要求 | MCP基础开发能力（能写Tool）、基础Python/TypeScript |
| 预计教学时长 | 3-4小时（含实操练习） |
| 实操占比 | 70%（大量代码重构练习） |
| 学员交付物 | 一个经过评估闭环验证的、准确率≥90%的MCP Server |
| 难度等级 | ⭐⭐⭐（中级） |

## 参考来源
1. [Writing effective tools for AI agents—using AI agents — Anthropic](https://www.anthropic.com/engineering/writing-tools-for-agents) — Anthropic官方工程博客，Tool描述设计的权威指南
2. [Block's Playbook for Designing MCP Servers](https://engineering.block.xyz/blog/blocks-playbook-for-designing-mcp-servers) — Block公司60+个MCP Server的生产级设计经验
3. [The Six-Tool Pattern: MCP Server Design That Scales](https://www.mcpbundles.com/blog/mcp-tool-design-pattern) — 六工具模式的完整设计方法论
4. [MCP Tool Annotations Explained — ChatForest](https://chatforest.com/guides/mcp-tool-annotations-explained/) — 五大Annotation Hint的实践解读
5. [MCP Specification 2025-06-18: Tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) — MCP协议Tool定义的官方规范

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
