MCP 服务开发:如何正确定义 Tools、Resources、Prompts

在开发 MCP 服务时,正确区分和定义三大原语是非常关键的。让我结合规范和实践,为你详细讲解。

一、三大原语的核心区别

MCP 协议定义了三种能力,每种都有不同的用途和行为模式:

维度 Tools(工具) Resources(资源) Prompts(提示模板)
本质 执行操作(类似 POST) 暴露数据(类似 GET) 工作流模板
是否只读 否(可能有副作用) 是(只读) 是(模板)
谁触发 AI 模型自动调用 客户端/主机加载 用户主动选择
需要输入 Schema 是 否* 是
模型如何看到 通过调用结果 通过主机注入上下文 通过消息对话

*资源模板可以有 URI 参数

二、Tools(工具)的规范定义

2.1 核心规范

Tools 是 MCP 服务器暴露给 AI 模型的可调用函数,每个工具必须有:

  • name:唯一标识符,长度 1-128 字符,只允许大小写字母、数字、下划线、连字符和点
  • description:人类可读的功能描述(这是 AI 判断何时调用工具的关键)
  • inputSchema:定义参数的 JSON Schema(默认为 2020-12 版本)

2.2 让 AI 正确理解的关键

description 是 AI 模型判断“什么时候该用这个工具”的唯一依据。写得好不好,直接决定 AI 会不会正确调用。最佳实践:

python
@mcp.tool()
def search_documents(
    query: str,
    categories: list[str] | None = None,
    max_results: int = 10
) -> list[dict]:
    """
    搜索内部文档库

    Args:
        query: 搜索关键词(必填)
        categories: 限制搜索的文档类别列表,如 ["api", "guide"]
        max_results: 最多返回结果数,1-50 之间
    """
    # 实现逻辑

编写判断原则:

  • 从 AI 的视角写描述:解释“这个工具做什么、什么时候用、返回什么、有什么限制”
  • 使用最具体的 Schema 类型:日期用 z.string().date(),邮箱用 z.string().email()
  • 提供示例参数:在描述中包含使用场景,帮助 AI 理解

2.3 错误处理规范

工具错误应通过结果对象报告,而不是协议级错误:

python
try:
    result = perform_operation()
    return {"content": [{"type": "text", "text": f"操作成功: {result}"}]}
except Exception as error:
    return {
        "isError": True,  # 标记为业务错误
        "content": [{"type": "text", "text": f"错误: {str(error)}"}]
    }

三、Resources(资源)的规范定义

3.1 核心规范

Resources 是通过 URI 唯一标识的只读数据

。URI 设计要语义化:

python
# 静态资源
@mcp.resource("config://app/settings")
def get_settings() -> str:
    """应用配置(只读)"""
    return json.dumps({"version": "1.0.0", "environment": "production"})

# 动态资源(包含 URI 参数)
@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
    """获取指定用户的档案"""
    user = db.query(f"SELECT * FROM users WHERE id = ?", [user_id])
    return json.dumps(user)

3.2 URI 设计原则

URI 应语义化、可读性强:

表格

示例 URI 含义
file:///path/to/file.ts 本地文件
postgres://host/db/table/schema 数据库结构
memory://context/conversation 内存数据
custom://myapp/config/settings 自定义协议

3.3 与 Tools 的选择标准

使用资源还是工具的核心判断:

  • 需要数据 ➔ Resource(AI 会直接读取)
  • 需要执行操作 ➔ Tool(AI 主动调用)
  • 数据频繁变化 ➔ Resource + 订阅
  • 操作有副作用 ➔ Tool

四、Prompts(提示模板)的规范定义

4.1 核心规范

Prompts 是可复用的交互模板,用于标准化工作流程:

python
# 静态 Prompt
@mcp.prompt()
def security_audit() -> str:
    """触发安全审计工作流"""
    return """
    请执行完整的安全审计:
    1. 使用 scan_dependencies 检查依赖漏洞
    2. 使用 check_config 验证安全配置
    3. 使用 find_secrets 扫描硬编码密钥
    4. 汇总所有发现,按 CVSS 评分排序
    5. 为每个问题提供修复建议
    """

# 带参数的动态 Prompt
@mcp.prompt()
def code_migration(
    from_framework: str,
    to_framework: str,
    file_path: str | None = None
) -> list[dict]:
    """生成代码迁移指导"""
    scope = f"文件 {file_path}" if file_path else "整个项目"
    return [{"role": "user", "content": {"type": "text", "text": f"..."]}}]

4.2 Prompts 的真正价值

Prompts 是最容易被忽视但实际上非常强大的原语。它的价值在于:

让团队的最佳实践标准化——你团队里最厉害的工程师的工作逻辑,可以固化成 Prompt,供所有工程师使用。

五、如何让 AI 正确理解和调用

5.1 描述的编写黄金法则

  1. 从 AI 视角出发:写描述时假设自己是 AI,需要什么信息来决定是否调用
  2. 具体而非抽象:❌ “天气工具” → ✅ “获取指定城市的当前天气信息,包括温度、湿度和风速”
  3. 明确限制条件:”仅支持美国城市” 防止 AI 错误调用
  4. 示例引导:在描述中加入典型使用场景

5.2 工具注释(Annotations)

向 AI 和客户端提供行为元信息:

typescript
{
  name: "delete_records",
  description: "删除数据库记录",
  inputSchema: { ... },
  annotations: {
    readOnlyHint: false,    // 会修改数据
    destructiveHint: true,  // 操作不可撤销
    idempotentHint: false,  // 不是幂等的
    openWorldHint: false    // 只影响本地系统
  }
}

注解影响客户端的行为:

  • readOnlyHint: true → 可自动执行,无需确认
  • destructiveHint: true → 要求用户明确确认

5.3 安全信任原则

客户端必须将工具注释视为不可信,除非它们来自受信任的服务器。

六、实战决策框架

当你开发时,可以这样判断:

场景 应该用哪个
查询数据库获取数据 Resource
修改数据库记录 Tool
搜索文档 Tool(因为有搜索逻辑)
暴露配置信息 Resource
代码审查流程 Prompt
发送邮件 Tool
Bug 报告模板 Prompt

总结

开发 MCP 服务时,遵循以下原则:

  1. Tools = 执行操作,使用 @mcp.tool(),description 要写详细
  2. Resources = 暴露数据,使用 @mcp.resource(),URI 设计要语义化
  3. Prompts = 工作流模板,使用 @mcp.prompt(),标准化最佳实践

AI 主要通过 description(描述)和 inputSchema(参数 Schema)来理解你的工具。描述写得越清晰、越具体,AI 就越能正确判断何时调用以及如何传递参数。这是整个 MCP 服务开发中最值得投入精力的地方。