Skip to content

资源、提示、工具契约与 Agent 接入 ​

三种原语有不同控制入口 ​

资源以 URI 标识可读取内容,提示定义可由用户选择的对话模板,工具暴露可调用行为。三者都可能返回文本,但生命周期和使用意图不同。把数据库查询包装成资源不自动消除权限问题,把资源包装成工具也不意味着模型必须调用。

在第一章 server.py 的 if __name__ 之前添加:

python
@mcp.resource('inventory://policy')
def policy() -> str:
    return '库存只读;unknown 表示未收录,0 表示售罄。'
@mcp.prompt()
def explain_stock(sku: str) -> str:
    return f'查询商品 {sku} 的库存,并区分售罄与未收录。'

客户端在初始化后的 session 范围内增加:

python
resources=await session.list_resources()
assert any(str(r.uri)=='inventory://policy' for r in resources.resources)
data=await session.read_resource('inventory://policy')
assert '售罄' in data.contents[0].text
prompts=await session.list_prompts()
assert any(p.name=='explain_stock' for p in prompts.prompts)
prompt=await session.get_prompt('explain_stock',{'sku':'B'})
assert '商品 B' in prompt.messages[0].content.text
print('resource and prompt passed')

再次运行 client.py,新进程会注册新增原语。列出资源不读取内容,获取提示不执行工具,只有 call_tool 才发生工具行为。这些观察能防止把目录发现和实际执行混为一谈。

如何接入模型工具循环 ​

先 list_tools 得到名称、描述和 inputSchema,转换为模型平台的工具声明;模型返回调用后,用原名称和校验过的参数调用 MCP,再将 CallToolResult 变成平台工具结果。必须保留两套 ID:模型 call ID 用于回传模型,MCP JSON-RPC ID 用于协议关联。

不同 Server 可能都提供 search,Host 应分配 server_alias__search 这样的内部唯一名,同时维护映射表,不能只用工具名在全局查找。Server 返回的资源文本和工具文本属于数据;其中出现“忽略原规则”不能升级成 Host 指令。

大工具结果需要预算:先限制服务端返回数量,再保留来源和可翻页游标;不要简单截断 JSON 字符串导致下一轮无法解析。list 接口可能分页,只有读取第一页不能声称发现全部工具。动态工具变更通知要求客户端刷新目录,旧 schema 的缓存有版本身份。

Schema 与授权不是一层 ​

SDK 根据函数签名生成参数 schema,使模型知道 sku 是字符串;服务端仍需校验合法 SKU、租户和资源范围。鉴权主体从受信任连接上下文取得,不相信模型参数里的 user_id。对路径工具做规范化和允许目录检查,符号链接解析后的路径也需要验证;仅拒绝字符串 .. 不够。

参阅 工具、资源 与 提示。

练习 ​

两个服务都有 search。实现一个纯 Python 映射,将 docs__search 路由到 docs/search,将 tickets__search 路由到 tickets/search;未知工具必须报错。

先记录自己的输出与解释,再核对本章答案。

用心记录每一步成长