Skip to content

5.2 设计模式与反模式

小周看了很多 MCP Server 的实现,发现好的设计有共性,坏的设计也有共性。这一节先总结 10 个常见误区,再给出 7 种好模式和 7 种坏模式。


十大误区

在学 MCP 的过程中,这些是最容易踩的坑:

#误区事实出处
1"我用了 Claude Code,所以一定要用 MCP"Claude Code 内置了大量工具,大部分任务不需要额外 MCP Server1.3
2"Tool Annotations 的 readOnlyHint 是可信的"Server 可以声称只读但实际执行写操作,Annotations 只是「标签」不是「保证」2.2, 4.1
3"在 stdio Server 中用 print() 输出调试"print() 写入 stdout,会破坏 JSON-RPC 消息解析,必须用 stderr3.3
4"MCP 能让 AI 更聪明"MCP 不提升 AI 能力,它只是让 AI 更容易「动手」——连接外部系统1.1
5"MCP 会替代 OpenAPI"MCP 和 OpenAPI 互补——MCP Server 内部可以调用 OpenAPI 定义的 API5.1
6"MCP 和 A2A 是竞争关系"MCP 连接 Agent↔工具,A2A 连接 Agent↔Agent,两者互补5.1
7"Protocol Error 和 Tool Execution Error 是一回事"Protocol Error LLM 无法修正,Tool Execution Error LLM 可以看到并修正参数重试2.3
8"远程 MCP Server 不需要安全措施"远程 Server 必须启用 TLS、OAuth 2.1、验证 Origin 头4.1
9"一个 Server 应该暴露所有能力"单一职责——每个 Server 只做一件事,做好一件事5.2
10"MCP 只属于 Anthropic"MCP 已移交 Linux Foundation,是开放标准。OpenAI 也支持 MCP1.2

7 种设计模式

好的设计

filesystem-server → 只负责文件读写

github-server → 只负责 GitHub API

database-server → 只负责数据库查询

坏的设计

all-in-one-server → 文件 + GitHub + 数据库 + 邮件 + ...

模式 1:单一职责 Server

每个 Server 只做一件事,做好一件事。

好处:易于理解、独立部署、独立扩展。

模式 2:ReadOnly 优先

优先暴露只读工具,写操作需要额外确认。

python
# 只读工具——安全,可直接暴露
@mcp.tool()
def search_files(pattern: str) -> str:
    """搜索文件"""
    ...

# 写操作——需要确认
@mcp.tool()
def delete_file(path: str) -> str:
    """删除文件(需用户确认)"""
    # 通过 Elicitation 请求用户确认
    ...

模式 3:Schema 严格验证

工具的 inputSchema 应尽可能严格——限制参数类型、范围、枚举值。

json
{
  "inputSchema": {
    "type": "object",
    "properties": {
      "action": {
        "type": "string",
        "enum": ["list", "get", "search"]
      },
      "limit": {
        "type": "integer",
        "minimum": 1,
        "maximum": 100
      }
    },
    "required": ["action"],
    "additionalProperties": false
  }
}

模式 4:结构化错误反馈

工具执行错误时,返回清晰的可操作反馈。

python
# ✅ 好的错误——LLM 能理解并修正
@mcp.tool()
def query_database(sql: str) -> str:
    try:
        result = db.execute(sql)
        return format_result(result)
    except SyntaxError as e:
        # Tool Execution Error——LLM 可以修正 SQL
        raise ValueError(f"SQL 语法错误:{e}。请检查 SQL 语句。")

# ❌ 坏的错误——LLM 无法修正
@mcp.tool()
def query_database(sql: str) -> str:
    result = db.execute(sql)  # 直接抛异常,没有有用的错误信息
    return result

模式 5:工具注解诚实

Tool Annotations 应如实反映工具行为。

python
@mcp.tool()
def search_files(pattern: str) -> str:
    """搜索文件"""
    # Annotations 如实声明:只读、幂等
    # (FastMCP 中通过装饰器参数设置)
    ...

不要声称 readOnlyHint: true 但实际执行写操作。

模式 6:渐进式信息暴露

Server 根据需要暴露不同粒度的信息。

Resources(只读数据):
  - todo://stats     → 统计信息(轻量)
  - todo://list      → 列表概览(中等)
  - todo://item/123  → 详细信息(完整)

Prompts(模板):
  - summarize       → 快速摘要
  - detailed-review → 深度分析

模式 7:无状态优先

工具尽量无状态,状态由 Client 或外部存储管理。

python
# ✅ 无状态——每次调用独立
@mcp.tool()
def search(query: str) -> str:
    return db.search(query)

# ❌ 有状态——依赖上次调用的结果
last_results = []
@mcp.tool()
def search(query: str) -> str:
    global last_results
    last_results = db.search(query)  # 状态存全局变量
    return last_results

7 种反模式

反模式 1:God Server

一个 Server 做所有事。

❌ 全能 Server:
- 100+ 工具
- 文件系统 + GitHub + 数据库 + 邮件 + 搜索 + ...
- 工具列表太长,LLM 难以选择
- 任何工具出问题都影响整体

反模式 2:print() 调试

在 stdio Server 中用 print() 输出调试信息——这会破坏 JSON-RPC 消息解析。

python
# ❌ 会破坏协议
print("调试信息")

# ✅ 写入 stderr
print("调试信息", file=sys.stderr)

# ✅ 或用 logging
import logging
logger = logging.getLogger(__name__)
logger.info("调试信息")

反模式 3:过度暴露

暴露不需要的工具和数据。

❌ 过度暴露:
- 数据库 Server 暴露了 DROP TABLE
- 文件 Server 暴露了 /etc/passwd
- GitHub Server 暴露了删除仓库的权限

反模式 4:忽略错误处理

工具执行出错时没有有用的错误信息。

python
# ❌ 吞掉错误
@mcp.tool()
def risky_tool(data: str) -> str:
    try:
        return process(data)
    except:
        return "Error"  # 什么错误?LLM 无法修正

# ✅ 提供可操作的错误信息
@mcp.tool()
def risky_tool(data: str) -> str:
    try:
        return process(data)
    except ValueError as e:
        raise ValueError(f"数据处理失败:{e}。请检查数据格式。")

反模式 5:松散 Schema

inputSchema 太松,LLM 可能传入意外的参数。

json
// ❌ 太松
{ "type": "object", "properties": { "input": {} } }

// ✅ 严格
{
  "type": "object",
  "properties": {
    "query": { "type": "string", "minLength": 1, "maxLength": 500 }
  },
  "required": ["query"],
  "additionalProperties": false
}

反模式 6:阻塞式长操作

工具执行时间过长,没有进度通知。

python
# ❌ 阻塞——Client 不知道进度
@mcp.tool()
def train_model(data: str) -> str:
    time.sleep(300)  # 5 分钟
    return "训练完成"

# ✅ 分步骤 + 进度通知(如果 SDK 支持)
@mcp.tool()
async def train_model(data: str) -> str:
    # 步骤 1
    await notify_progress("正在预处理数据...", 1, 3)
    # 步骤 2
    await notify_progress("正在训练模型...", 2, 3)
    # 步骤 3
    await notify_progress("正在评估结果...", 3, 3)
    return "训练完成"

反模式 7:忽略安全注解

不设置 Tool Annotations,或者设置不诚实的注解。

python
# ❌ 没有注解——Client 不知道这个工具是否安全
@mcp.tool()
def delete_all() -> str:
    db.delete_all()
    return "已删除所有数据"

# ✅ 诚实设置注解
@mcp.tool(annotations={"destructiveHint": True, "readOnlyHint": False})
def delete_all() -> str:
    db.delete_all()
    return "已删除所有数据"

编排模式——多 Server 协作实例

MCP 架构下 Server 之间不直接通信,由 AI(Client/Host 层)编排。来看一个真实的对话示例:

用户
"检查项目中的 TODO 注释,把重要的记录到数据库,然后在 GitHub 创建 Issue"
AI 编排
好的,我先搜索代码中的 TODO 注释
filesystem-server → search-files
搜索代码中的 TODO 注释
发现 5 处 TODO,其中 2 处标记为"重要"
AI 编排
找到 2 处重要 TODO,现在记录到数据库
database-server → insert
插入 2 条重要 TODO 记录
成功插入,ID 为 #42、#43
AI 编排
已记录到数据库,现在在 GitHub 创建 Issue
github-server → create-issue
创建 2 个 GitHub Issue
创建了 issue #128、#129
AI 编排
已完成!在代码中找到 5 处 TODO,其中 2 处标记为重要。已记录到数据库(#42、#43),并在 GitHub 创建了 Issue(#128、#129)。

关键点:三个 Server 各司其职,AI 是编排者。 这就是 MCP 架构的优雅之处——不需要复杂的 Server 间通信协议。


检查清单

设计 MCP Server 时,用这个清单自查:

  • [ ] 每个 Server 是否只做一件事?(单一职责)
  • [ ] 只读工具是否标记 readOnlyHint?(注解诚实)
  • [ ] inputSchema 是否严格?(限制类型、范围)
  • [ ] 错误信息是否可操作?(LLM 能修正)
  • [ ] 是否只暴露必要的工具?(最小权限)
  • [ ] stdio Server 是否没有 print()?(不污染 stdout)
  • [ ] 是否没有全局状态?(无状态优先)
  • [ ] 长操作是否有进度通知?(非阻塞)

本节核心要点

  • 7 种好模式:单一职责、ReadOnly 优先、Schema 严格、结构化错误、诚实注解、渐进暴露、无状态优先
  • 7 种坏模式:God Server、print() 调试、过度暴露、忽略错误、松散 Schema、阻塞长操作、忽略注解
  • 核心原则:最小权限、严格验证、诚实标注、有用反馈

练习:检查你之前写的 Todo Server,对照检查清单看看有哪些可以改进的地方。


← 上一节:协议大比拼 | 目录 | 下一节:MCP 生态全景 →