1. 资源是什么? #
资源(Resources) 是 MCP 服务器暴露的只读数据,供 AI 或用户当作上下文参考。
| 对比 | 资源(Resources) | 工具(Tools) |
|---|---|---|
| 作用 | 读数据 | 做事情 |
| 谁决定用 | 用户 / 应用 | 大模型 |
| 类比 | 打开文档看一眼 | 执行一条命令 |
| 会改数据吗 | 不会(只读) | 可能会 |
典型例子:应用配置、用户偏好、某篇笔记、文件内容、数据库查询结果。
你需要知道的三个协议方法:
| 方法 | 作用 | 客户端 SDK 写法 |
|---|---|---|
resources/list |
列出静态资源 | await session.list_resources() |
resources/templates/list |
列出资源模板 | await session.list_resource_templates() |
resources/read |
读取指定 URI 的内容 | await session.read_resource(uri) |
2. 两种资源类型 #
每个资源用 URI 唯一标识(像网址,但不必真是 http)。
| 类型 | URI 特征 | 函数参数 | 示例 |
|---|---|---|---|
| 静态资源 | 地址固定,无 {} |
无参数 | config://app |
| 模板资源 | 含 {参数} 占位符 |
参数名与占位符一致 | note://{title} → note://周报 |
生活类比:
- 静态资源 = 图书馆里编号固定的书
- 模板资源 = 「借书:书名=___」的表格,填上书名就能取到对应内容
3. 读取资源的流程 #
sequenceDiagram
participant App as 客户端/应用
participant S as MCP服务器
App->>S: resources/list
S-->>App: [config://app, ...]
App->>S: resources/templates/list
S-->>App: [note://{title}, ...]
App->>S: resources/read(uri)
S-->>App: 资源文本内容
Note over App: 把内容放进 AI 上下文
与工具不同:资源不会由模型自动「调用」,通常由应用或用户选择后,再 read_resource 交给大模型。
4. 资源在协议里长什么样? #
4.1 静态资源列表(list 响应片段) #
{
"resources": [
{
"uri": "config://app",
"name": "config://app",
"mimeType": "application/json"
}
]
}4.2 资源模板列表 #
{
"resourceTemplates": [
{
"uriTemplate": "note://{title}",
"name": "note://{title}",
"mimeType": "text/plain"
}
]
}4.3 读取资源(read 响应片段) #
{
"contents": [
{
"uri": "config://app",
"mimeType": "application/json",
"text": "{\"theme\": \"dark\", \"lang\": \"zh-CN\"}"
}
]
}5. 服务器端:注册资源 #
用 `@mcp.resource("uri")装饰函数即可,FastMCP 自动处理resources/list和resources/read`。
5.1 静态资源 #
# 导入 json 模块,用于操作 JSON 数据
import json
# 从 mcp.server.fastmcp 中导入 FastMCP 类
from mcp.server.fastmcp import FastMCP
# 实例化 FastMCP 对象,名称为 "resource-demo"
mcp = FastMCP(name="resource-demo")
# 使用 @mcp.resource 装饰器注册资源,指定 URI 和 mime_type
@mcp.resource("config://app", mime_type="application/json")
# 定义 app_config 函数,返回类型为 str
def app_config() -> str:
# 为该函数添加文档字符串说明,表示为应用配置(静态资源,无参数)
"""应用配置(静态资源,无参数)。"""
# 将字典转为 JSON 字符串,不转义中文,返回结果
return json.dumps({"theme": "dark", "lang": "zh-CN"}, ensure_ascii=False)
# 使用 @mcp.resource 装饰器注册模板资源,URI 中 {title} 占位符会传递给函数参数 title,指定返回的内容类型为 text/plain
@mcp.resource("note://{title}", mime_type="text/plain")
# 定义 read_note 函数,接收一个字符串类型的 title 参数,返回一个字符串
def read_note(title: str) -> str:
# 添加函数文档字符串,说明这是“按标题读取笔记”
"""按标题读取笔记。"""
# 返回包含标题和示例内容的字符串,格式化插入 title
return f"笔记标题:{title}\n内容:这是 MCP 资源示例。"
# 判断当前模块是否为主程序入口
if __name__ == "__main__":
# 启动 MCP 服务,采用 stdio 作为传输方式
mcp.run(transport="stdio")客户端读取时传完整 URI:note://weekly-report → 服务器调用 read_note(title="weekly-report")。
5.2 常用 URI 方案 #
| 方案 | 示例 | 说明 |
|---|---|---|
| 自定义 | config://app |
最常见,自己定义 scheme |
file:// |
file:///D:/docs/readme.md |
文件系统服务器常用 |
https:// |
远程 URL | 客户端能直接拉取时用 |
6. 客户端:列出并读取资源 #
初始化完成后,依次 list_resources → list_resource_templates → read_resource。
6.1 示例 #
resources_client.py
# 导入 asyncio 模块,用于异步编程
import asyncio
# 导入 os 模块,用于处理文件和目录
import os
# 导入 sys 模块,获取解释器信息
import sys
# 从 pydantic 导入 AnyUrl,用于 URL 类型的数据验证
from pydantic import AnyUrl
# 从 mcp 包导入 ClientSession、StdioServerParameters 和 types
from mcp import ClientSession, StdioServerParameters, types
# 从 mcp.client.stdio 导入 stdio_client,用于标准输入输出通信
from mcp.client.stdio import stdio_client
# 定义一个异步主函数
async def main() -> None:
# 获取当前文件的绝对路径并获取其所在目录
base_dir = os.path.dirname(os.path.abspath(__file__))
# 拼接出服务器脚本的路径
server_path = os.path.join(base_dir, "resources_server.py")
# 创建 StdioServerParameters 实例,指定命令和参数
server_params = StdioServerParameters(
command=sys.executable,
args=[server_path],
)
# 使用 stdio_client 启动服务器,获取读写对象
async with stdio_client(server_params) as (read, write):
# 创建客户端会话,传入读写对象
async with ClientSession(read, write) as session:
# 初始化会话
await session.initialize()
# 列出静态资源
resources = await session.list_resources()
# 打印所有静态资源的 URI
print("静态资源:", [str(r.uri) for r in resources.resources])
# 列出资源模板
templates = await session.list_resource_templates()
# 打印所有资源模板的 URI 模板
print("资源模板:", [t.uriTemplate for t in templates.resourceTemplates])
# 读取静态资源,URI 为 config://app
result = await session.read_resource(AnyUrl("config://app"))
# 遍历返回的内容块
for block in result.contents:
# 如果内容块是文本资源内容类型
if isinstance(block, types.TextResourceContents):
# 打印文本内容
print("[config://app]", block.text)
# 读取模板资源,URI 填入参数值(建议英文路径,中文易被编码)
result = await session.read_resource(AnyUrl("note://weekly-report"))
# 遍历返回的内容块
for block in result.contents:
# 如果内容块是文本资源内容类型
if isinstance(block, types.TextResourceContents):
# 打印文本内容
print("[note://weekly-report]", block.text)
# 判断是否为主模块运行
if __name__ == "__main__":
# 运行主异步函数
asyncio.run(main())6.2 运行 #
uv run resources_client.py 期望输出:
静态资源: ['config://app']
资源模板: ['note://{title}']
[config://app] {"theme": "dark", "lang": "zh-CN"}
[note://weekly-report] 笔记标题:weekly-report
内容:这是 MCP 资源示例。6.3 API 说明 #
| 方法 | 返回 | 用途 |
|---|---|---|
list_resources() |
.resources 列表 |
发现所有静态资源 URI |
list_resource_templates() |
.resourceTemplates 列表 |
发现带占位符的模板 |
read_resource(AnyUrl(...)) |
.contents 列表 |
获取实际文本/二进制内容 |
6.4 读取返回内容 #
read_resource 的 contents 里每个块可能是:
| 类型 | 字段 | 说明 |
|---|---|---|
TextResourceContents |
.text |
文本内容(最常用) |
BlobResourceContents |
.blob |
base64 编码的二进制 |
判断方式:
# 导入 mcp 模块中的 types 类型定义
from mcp import types
# 遍历 result.contents 中的每一个内容块
for block in result.contents:
# 判断当前块是否为 TextResourceContents 类型(即文本资源内容)
if isinstance(block, types.TextResourceContents):
# 打印出文本内容
print(block.text)7. 用 Inspector 测试资源 #
npx @modelcontextprotocol/inspector uv run resources_server.py| 步骤 | 操作 |
|---|---|
| 1 | 确认 Connected |
| 2 | 打开 Resources 标签 |
| 3 | 在列表里点 config://app 预览内容 |
| 4 | 对模板资源,在 URI 栏输入 note://测试 后读取 |