第 17 章 · 核心原语:Tools/Resources/Prompts
本章目标:掌握 MCP Server 可暴露的三大原语——Tools、Resources、Prompts 的分工与控制权归属,理解发现机制,学会按场景为能力选择正确的原语。
11.1 三大原语与控制权归属
MCP 定义了 Server 可以向 Client 暴露的三种核心原语。记住它们的分工固然重要,但更重要的是记住谁决定何时使用(control 权归属):
| 原语 | 一句话定义 | 控制权 | 典型交互 |
|---|---|---|---|
| Tools | 模型可调用的可执行函数 | 模型控制(model-controlled) | 模型根据上下文自主决定调用 |
| Resources | 为模型提供上下文的数据源 | 应用控制(application-driven) | Host 应用决定如何纳入上下文 |
| Prompts | 结构化的提示模板 | 用户控制(user-controlled) | 用户显式选择触发(如斜杠命令) |
官方文档反复强调这个三角关系:Tools 面向"让模型做事",Resources 面向"给应用喂数据",Prompts 面向"让用户一键触发预设流程"。每个原语都配有 */list(发现)、*/get(获取)方法,Tools 还多一个 tools/call(执行)。这种设计让列表可以是动态的。
11.2 Tools:模型的双手
Tools 让模型能够执行动作——查数据库、调 API、做计算。每个工具由唯一名称和描述其参数 schema 的元数据标识。
支持 Tools 的 Server 必须在能力中声明 tools.listChanged: true,表示"工具列表可能变化,客户端应监听 notifications/tools/list_changed 通知后重新拉取"。一次完整的调用流程如下:
// ① 客户端发现工具
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }
// ② 服务端返回工具清单(含 JSON Schema 参数描述)
{
"jsonrpc": "2.0", "id": 1,
"result": {
"tools": [{
"name": "get_weather",
"description": "查询指定城市的当前天气",
"inputSchema": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}]
}
}
// ③ 模型决定调用后,客户端发起执行
{
"jsonrpc": "2.0", "id": 2,
"method": "tools/call",
"params": { "name": "get_weather", "arguments": { "city": "上海" } }
}安全红线
规范明确:出于信任与安全考虑,应当始终保留人工在环(human in the loop)的能力来拒绝工具调用。应用应该向用户清晰展示暴露了哪些工具、在工具被调用时给出可视化指示、对敏感操作弹出确认。
11.3 Resources:应用的原料库
Resources 是由 URI 唯一标识的数据源——文件内容、数据库 schema、API 响应等。它的关键特征是应用驱动:Server 把数据摆出来,由 Host 应用决定如何使用(比如通过 UI 让用户挑选哪些文件挂进对话),而不是让模型自主抓取。
# mcp_server_resources.py —— 用 FastMCP 声明资源
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("knowledge-base")
@mcp.resource("docs://{doc_name}")
def get_doc(doc_name: str) -> str:
"""暴露静态文档资源,URI 形如 docs://readme"""
return open(f"./docs/{doc_name}.md").read()
@mcp.resource("config://app-settings")
def get_settings() -> str:
"""动态资源:每次获取时读取最新配置"""
import json
return json.dumps(load_settings(), ensure_ascii=False)Resources 支持订阅机制:客户端可以对某个资源调用 resources/subscribe,当资源变化时收到 notifications/resources/updated 通知再重新拉取——避免轮询浪费。
11.4 Prompts:用户的快捷指令
Prompts 是 Server 提供的可复用提示模板,带参数、可定制。"user-controlled"指的是由用户决定何时使用(模板内容仍是 Server 作者写的)。最自然的呈现方式是聊天输入框里的斜杠命令:
/code-review PR#123 ← 用户敲入,Client 取回 prompt 模板并填充参数# mcp_server_prompts.py —— FastMCP 声明提示模板
@mcp.prompt()
def review_code(code: str, focus: str = "安全性") -> str:
"""代码审查模板:用户在界面中选择并填参后触发"""
return f"""请审查以下代码,重点关注{focus}问题:
输出格式:问题列表(按严重程度排序)+ 修改建议。"""注意区分:Prompts 是用户主动选的模板;而第 6 章讲过的 system prompt 是应用写死的。前者是 Server 提供给用户的"快捷方式"。
11.5 原语选型决策表与综合案例
面对一个新能力,用这张表快速定原语:
| 你的需求 | 选什么 |
|---|---|
| 模型需要执行某个动作(查询/写入/计算) | Tool |
| 应用想把某份数据作为上下文提供给模型,且由应用/用户决定时机 | Resource |
| 用户经常要触发一段固定的复杂提问流程 | Prompt |
| 数据 + 执行都要(如"数据库") | 组合使用三者 |
官方架构文档给出的综合案例正是一个数据库 MCP Server:
# db_server.py —— 一个 Server 同时暴露三种原语
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("company-db")
# Tool:模型可调用的查询动作(model-controlled)
@mcp.tool()
def run_query(sql: str) -> str:
"""对只读副本执行 SELECT 查询,返回结果表格"""
return execute_readonly_sql(sql)
# Resource:数据库 schema 作为上下文数据(application-driven)
@mcp.resource("db://schema/users")
def users_schema() -> str:
return open("./schemas/users.sql").read()
# Prompt:封装"分析用户增长"的标准问法(user-controlled)
@mcp.prompt()
def growth_analysis(metric: str) -> str:
return f"""请基于 users 表(schema 见已加载的资源)分析近 90 天的{metric}趋势,
先用 run_query 工具获取数据,再给出三个关键结论。"""
if __name__ == "__main__":
mcp.run() # 默认 stdio 传输启动三种原语协作:用户敲 /growth-analysis 日活 触发 Prompt → Client 加载 Resource 中的 schema 进上下文 → 模型看到 run_query 工具并决定调用 → 结果回流生成最终分析。
本章小结
- 三大原语的控制权三角:Tools 模型控制、Resources 应用驱动、Prompts 用户触发;
- 所有原语都有
*/list动态发现;Tools 额外有tools/call执行与listChanged变更通知; - Resources 用 URI 标识、支持订阅更新;Prompts 以斜杠命令等形式由用户显式触发;
- 规范要求保留 human-in-the-loop 拒绝工具调用的能力,高危操作必须确认;
- 复杂场景组合使用三原语:Tool 管动作、Resource 管上下文数据、Prompt 管标准流程。
🛠️ 动手实践
- 用 FastMCP 写一个"笔记库" Server:暴露 add_note / search_notes 两个 Tool 和一个 notes://all 资源,用 MCP Inspector 测试全部功能。
- 给你的笔记 Server 加一个 weekly-summary Prompt 模板,参数为日期范围,验证它在 Inspector 中的呈现形式。
- 思考并写下你工作中的一个内部系统:把它的功能逐一分类到 Tool/Resource/Prompt 三类中,标注每项的控制权归属,找出分类模糊的边界情况。
完成练习后,进入。
完成后进入下一章:传输层与生命周期。