在开发 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 描述的编写黄金法则
- 从 AI 视角出发:写描述时假设自己是 AI,需要什么信息来决定是否调用
- 具体而非抽象:❌ “天气工具” → ✅ “获取指定城市的当前天气信息,包括温度、湿度和风速”
- 明确限制条件:”仅支持美国城市” 防止 AI 错误调用
- 示例引导:在描述中加入典型使用场景
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 服务时,遵循以下原则:
- Tools = 执行操作,使用
@mcp.tool(),description 要写详细 - Resources = 暴露数据,使用
@mcp.resource(),URI 设计要语义化 - Prompts = 工作流模板,使用
@mcp.prompt(),标准化最佳实践
AI 主要通过 description(描述)和 inputSchema(参数 Schema)来理解你的工具。描述写得越清晰、越具体,AI 就越能正确判断何时调用以及如何传递参数。这是整个 MCP 服务开发中最值得投入精力的地方。
