RSS

第 4 章 / 共 12 章

工具、资源、提示词:谁来决定调用

约 8 分钟 更新于

4.1 同一个需求,三种做法,代价完全不同

现在给笔记库加第二个能力:读取某一篇笔记的全文。

这件事至少有三种做法,而且三种都合法:

  • 做成工具 read_note(filename),模型想读的时候自己调。
  • 做成资源 notes://reading.md,宿主应用把它列出来,由应用或用户决定要不要放进上下文。
  • 做成提示词 /review-note,用户在输入框里显式触发,展开成一段带笔记内容的提示。

选错不会报错,但会让你的 server 用起来别扭——模型该调的时候不调,或者上下文被塞满了用不上的东西。

三者的区别只有一条,而且规范写得非常清楚:谁来决定它被使用。

原语规范用词谁决定典型触发场景
Toolsmodel-controlled模型模型根据对话内容自主判断要不要调
Resourcesapplication-driven宿主应用应用把它列在侧栏、做成 @ 引用、或按策略自动附带
Promptsuser-controlled用户用户显式选择,常见形态是斜杠命令
第4章:工具、资源、提示词:谁来决定调用
图 4.1:左右两栏的差别不在能力,在控制权。选原语就是在回答一个问题:这件事该由谁来按下按钮。

4.2 工具:模型自己决定要不要用

工具是模型控制的。规范原文说,模型可以基于它对上下文的理解和用户的提问,自动发现并调用工具。

这也是为什么工具比另外两个更需要小心。它有副作用的能力,而且触发时机不由人决定。规范因此配了一条硬性的安全建议:出于信任与安全考虑,应该始终有一个人在回路里,有能力拒绝工具调用。

工具的名字有规则(2025-11-25 版本开始明确):长度建议在 1 到 128 个字符之间,区分大小写,只用大小写字母、数字、下划线、连字符和点。get_user、DATA_EXPORT_v2、admin.tools.list 都是合法的。

什么该做成工具:需要执行动作、需要参数、结果依赖调用时刻的事情。搜索、查询、写入、调用外部 API,都是工具。

4.3 资源:把数据摆出来,让宿主决定怎么用

资源是应用驱动的。规范特意用了 “application-driven” 这个词——宿主应用根据自己的需要决定怎么把资源纳入上下文。文档举的例子包括:在界面上做成树形或列表让用户显式选择、让用户搜索和筛选资源、或者按启发式规则自动附带。

也就是说,你没法控制资源会不会被读,这取决于宿主怎么实现。有的客户端把资源做得很显眼,有的客户端干脆不支持资源。这是选原语时的现实考量。

资源用 URI 标识。SDK 里写成这样:

@mcp.resource("notes://index")
def notes_index() -> str:
    """笔记库的目录,列出所有笔记的文件名与首行标题。"""
    lines = []
    for path in sorted(NOTES_DIR.glob("*.md")):
        first = path.read_text(encoding="utf-8").splitlines()[0]
        lines.append(f"{path.name}\t{first.lstrip('# ').strip()}")
    return "\n".join(lines)

URI 里带 {} 变量的话,它就变成一个资源模板,按 RFC 6570 解析:

@mcp.resource("notes://{filename}")
def read_note(filename: str) -> str:
    """按文件名读取一篇笔记的全文。"""
    return (NOTES_DIR / filename).read_text(encoding="utf-8")

有三条规则会在你写错时立刻报错,值得先知道:

  • URI 模板里的变量集合,必须和函数参数集合完全相等,多一个少一个都会在装饰阶段抛 ValueError。
  • 静态 URI 对应的函数不能有参数,也不能注入上下文对象。
  • 返回 str 是文本,返回 bytes 是二进制,返回别的会被转成 JSON。

什么该做成资源:相对稳定、可以被整份读取、用户或应用能提前判断要不要看的东西。文档、配置、目录清单、数据库 schema。

4.4 提示词:把一段专业流程包装成一条命令

提示词是用户控制的。规范说它们被暴露出来,就是为了让用户能显式选择使用,典型形态是斜杠命令。

@mcp.prompt()
def review_note(filename: str) -> str:
    """把一篇笔记整理成要点清单。"""
    body = (NOTES_DIR / filename).read_text(encoding="utf-8")
    return f"请把下面这篇笔记整理成不超过五条的要点清单,保留笔记里出现的日期与人名:\n\n{body}"

它返回的可以是字符串、消息列表、图片、音频等。消息里的角色只能是 user 或 assistant。

什么该做成提示词:你希望人来发起、并且提示词本身有专业价值的流程。「按我们团队的规范审查这段代码」「把这份会议记录整理成待办」——这类知识写在提示词里,比指望用户每次自己描述要可靠得多。

4.5 笔记库的能力清单

把上面三节应用到我们的例子上,就得到了这份清单。这张表就是接下来八章要实现的东西:

能力原语为什么是它副作用
搜索笔记 search_notes工具需要参数、结果随时在变、模型该自主判断何时搜无(只读)
读取笔记 read_note工具模型搜到之后往往要接着读,交给模型自己接力最顺无(只读)
笔记目录 notes://index资源相对稳定、适合整份呈现、用户可能想手动挑一篇无(只读)
追加笔记 append_note工具有写入动作,必须是工具有(会改文件)
整理成要点 review_note提示词由人发起,价值在那段提示词本身无

有一个判断技巧可以帮你快速定位:先问这件事有没有副作用。有副作用(写、删、发送、扣款)就只能是工具,没得选。没有副作用,再问「谁最适合决定这件事该不该做」,答案就是原语。

还有一个现实提醒:工具的客户端支持度最好,资源和提示词各家实现差别较大。如果你的 server 要给多种客户端用,核心能力放在工具里最稳妥,资源和提示词当增强。

4.6 练习与检查点