Resources原语:让AI读取你的数据
摘要MCP Resources原语让AI按需读取外部数据源支持静态URI和模板URI。本文详解资源定义、订阅通知、分页读取和与Tools原语的协作模式。Resources原语让AI读取你的数据上个月我给团队搭了个代码审查助手想让模型读项目里的配置文件和日志。我第一反应是用Tools写个read_file工具结果发现模型每次都要决定调用一下对话里塞满了工具调用的噪音而且同样的文件读了好几遍。后来我改用MCP的Resources原语把文件作为资源暴露出去客户端自己决定何时把内容塞进上下文对话干净多了。这篇我把Resources从URI设计到订阅机制完整讲一遍。Resources是什么MCP规范里Resources原语让Server以标准化方式向客户端暴露数据。每个资源用唯一的URI标识可以是文件、数据库表结构、配置信息任何能给模型提供上下文的数据都行。Resources有一个核心定位要记住它是application-driven的由宿主应用决定怎么使用这些资源。应用可以把资源做成树形列表让用户点选也可以根据启发式规则自动把相关资源塞进上下文。这和Tools的model-controlled完全相反Resources的主动权在应用和用户手里模型只是被动地读到了数据。Resources还有一个天然属性它是只读的。你拿URI去read拿到内容就这么简单。要修改数据请走ToolsResources只负责提供上下文。URI设计与资源模板每个Resource靠URI唯一标识。规范里列了几种常用schemefile://表示文件系统资源https://表示Web资源git://表示版本控制集成你也可以自定义scheme比如config://、db://只要符合RFC3986就行。Resources分两种。一种是静态资源URI固定比如config://app-settings指向一份配置。另一种是资源模板URI里带参数占位符用RFC 6570的URI Template语法比如file:///{path}客户端填入不同的path就能读不同文件。资源模板的发现走resources/templates/list和普通的resources/list分开。客户端先拿到模板列表知道有哪些参数化的资源可用再根据需要构造具体URI去read。下面是规范里的模板示例。{uriTemplate:file:///{path},name:Project Files,description:Access files in the project directory,mimeType:application/octet-stream}我用FastMCP定义资源模板特别方便URI里写{name}占位符函数签名里加同名参数就行框架自动匹配。静态资源和动态资源静态资源的内容是固定的比如一份配置文件、一段说明文字。动态资源的内容由函数实时生成比如当前系统状态、最近一小时日志。两者在FastMCP里都用mcp.resource装饰器区别在于函数有没有参数。资源内容支持两种格式。文本内容放在text字段二进制内容用base64编码放在blob字段配合mimeType标识类型。我做过一个把项目里PNG图标当资源暴露的实验二进制走blob字段客户端拿到base64解码就能显示。规范还定义了annotations给客户端提供使用提示。audience标识内容给谁看可选user和assistant。priority从0到1表示重要性1最重要。lastModified是最后修改时间。这些注解帮客户端决定要不要把资源塞进上下文、塞进去的优先级。订阅机制Resources支持两种通知机制。第一种是listChanged资源列表变化时Server推notifications/resources/list_changed客户端重新拉列表。第二种是subscribe客户端对某个具体URI发resources/subscribe请求之后这个资源内容变了Server就推notifications/resources/updated客户端再read一次拿最新内容。订阅机制对日志类资源特别有用。我那个代码审查助手暴露了一个logs://recent资源客户端订阅它每次有新日志进来Server推通知客户端自动刷新模型就能看到最新报错。我踩过一个坑Server声明了subscribe能力但忘了在内容变化时发updated通知客户端一直拿到旧数据查了半天才发现是通知没发。订阅的整个过程一定要端到端测一遍。完整代码下面是一个完整的Resources示例包含静态资源、动态资源模板和目录资源。客户端测试脚本读取这些资源。server.py# server.py MCP Resources原语完整示例# 运行方式 python server.py# 依赖安装 pip install fastmcpimportjsonfrompathlibimportPathfromfastmcpimportFastMCP,Context# 创建服务器实例mcpFastMCP(nameDataResourcesServer)# 模拟一个项目目录, 后面目录资源会用到PROJECT_DIRPath(./sample_project)PROJECT_DIR.mkdir(exist_okTrue)# 写一个示例文件, 方便测试读取(PROJECT_DIR/notes.txt).write_text(这是项目的备注文件, 记录待办事项.,encodingutf-8)# ---------- 静态资源, URI固定, 无参数 ----------mcp.resource(config://app-settings)defget_app_settings()-str:返回应用配置, 以JSON字符串形式提供. 静态资源的典型用法, URI写死, 客户端直接read这个URI就能拿到内容. settings{app_name:CodeReviewBot,version:2.1.0,max_file_size_mb:10,enabled_checks:[lint,type-check,security],}# 返回字符串, FastMCP会当作TextResourceContents处理returnjson.dumps(settings,ensure_asciiFalse,indent2)# ---------- 动态资源模板, URI带参数占位符 ----------mcp.resource(file:///{path})defread_project_file(path:str)-str:根据路径读取项目文件内容. 这是一个资源模板, URI里的{path}会映射到函数的path参数. 客户端构造 file:///notes.txt 就能读 sample_project/notes.txt. # 拼接完整路径, 注意防止路径穿越攻击full_pathPROJECT_DIR/pathifnotfull_path.exists():# 文件不存在时返回提示, 而不是抛异常returnf文件不存在{path}# 读取并返回文件内容returnfull_path.read_text(encodingutf-8)# ---------- 动态资源, 实时生成日志 ----------mcp.resource(logs://recent)asyncdefget_recent_logs(ctx:Context)-str:返回最近的系统日志, 内容实时生成. 这个资源每次读取都会重新生成内容, 适合配合订阅机制使用, 客户端订阅后能拿到最新日志. # 用Context记录一条调试日志, 会发回客户端awaitctx.debug(正在生成最近日志)logs[[2026-08-09 10:00:01] INFO 服务启动完成,[2026-08-09 10:00:05] WARN 内存使用率偏高 78%,[2026-08-09 10:00:10] ERROR 文件解析失败 notes.txt 第3行,]return\n.join(logs)# ---------- 目录资源, 列出目录下的文件 ----------mcp.resource(dir://project-files)deflist_project_files()-str:列出项目目录下的所有文件. 返回JSON格式的文件列表, 方便客户端知道有哪些文件可以读. files[]forfinPROJECT_DIR.iterdir():files.append({name:f.name,size:f.stat().st_size,is_dir:f.is_dir(),})returnjson.dumps(files,ensure_asciiFalse,indent2)if__name____main__:# 以stdio模式启动mcp.run()client_test.py# client_test.py Resources客户端测试# 运行方式 python client_test.pyimportasynciofromfastmcpimportClientasyncdefmain():asyncwithClient(server.py)asclient:# 第一步, 列出所有静态资源, 相当于发resources/listresourcesawaitclient.list_resources()print( 静态资源列表 )forrinresources:print(f URI{r.uri})print(f 名称{r.name})print()# 第二步, 列出资源模板, 相当于发resources/templates/listtemplatesawaitclient.list_resource_templates()print( 资源模板列表 )fortintemplates:print(f 模板{t.uriTemplate})print(f 名称{t.name})print()# 第三步, 读取静态资源, 相当于发resources/readprint( 读取 config://app-settings )contentsawaitclient.read_resource(config://app-settings)print(f 内容{contents[0].content})print()# 第四步, 用模板构造URI读取动态资源print( 读取 file:///notes.txt )contentsawaitclient.read_resource(file:///notes.txt)print(f 内容{contents[0].content})print()# 第五步, 读取实时日志print( 读取 logs://recent )contentsawaitclient.read_resource(logs://recent)print(f 内容{contents[0].content})if__name____main__:asyncio.run(main())效果验证装好fastmcp后跑client_test.py输出大致如下。 静态资源列表 URI config://app-settings 名称 get_app_settings URI logs://recent 名称 get_recent_logs URI dir://project-files 名称 list_project_files 资源模板列表 模板 file:///{path} 名称 read_project_file 读取 config://app-settings 内容 { app_name: CodeReviewBot, version: 2.1.0, max_file_size_mb: 10, enabled_checks: [lint, type-check, security] } 读取 file:///notes.txt 内容 这是项目的备注文件, 记录待办事项. 读取 logs://recent 内容 [2026-08-09 10:00:01] INFO 服务启动完成 ...客户端先list出三个静态资源和一个资源模板再用具体URI分别read拿到内容。在真实MCP客户端里这些资源会以列表或树形展示给用户用户选择后内容自动进入模型上下文。与Tools的区别和使用场景选择Resources和Tools经常被搞混我做了个对比。维度ResourcesTools控制方应用和用户主导(application-driven)模型主导(model-controlled)操作类型只读, 提供上下文数据可执行, 产生副作用标识方式URI唯一标识name唯一标识调用方式客户端按需read模型发call请求返回内容文本或二进制数据结构化结果或文本典型场景读文件、看配置、查日志查数据库、调API、发邮件选择标准很简单。如果只是让模型看到某些数据用Resources。如果要让模型执行一个动作产生结果或副作用用Tools。我踩过一个选择错误的坑。有个查用户信息的需求我一开始用Resources暴露user://123结果发现模型没法主动触发查询只能等应用把资源塞进去。后来改成Tools的get_user工具模型需要的时候自己调。反过来读项目README这种被动提供上下文的场景用Resources就比Tools合适得多避免了每次都要模型决定调用。常见问题与避坑坑1资源模板参数名和URI占位符对不上。FastMCP靠URI里{name}和函数参数name同名来匹配。写成了file:///{filepath}但函数参数叫path客户端调用时参数传不进去read到的永远是空内容。占位符和参数名务必一致。坑2路径穿越导致安全问题。资源模板直接拼用户传入的path读文件攻击者构造file:///…/…/…/etc/passwd就能读到敏感文件。一定要做路径校验把路径限制在允许的根目录内用resolve()检查是否越界。坑3订阅通知发了但内容没更新。Server推了notifications/resources/updated但实际资源函数返回的还是旧数据。检查你的资源函数是不是有缓存或者数据源没真正更新。订阅的整条流程要端到端验证发了通知就read一次确认内容是新的。坑4二进制资源忘了设mimeType。返回bytes类型的内容会被base64编码放进blob字段但mimeType默认是application/octet-stream。客户端不知道怎么处理图片显示不出来。在装饰器里显式指定mime_typeimage/png这类正确的类型。坑5把Resources当Tools用。Resources是只读的没有执行的概念。有人想在资源读取时顺带修改数据库这违反了Resources的只读语义。要产生副作用请用ToolsResources保持纯净的读取职责。小结Resources原语解决的是让模型读到你的数据这个问题。核心要点有四个每个资源用URI唯一标识资源模板用RFC 6570语法支持参数化订阅机制让客户端拿到资源更新通知annotations提供使用提示帮客户端做决策。和Tools相比Resources是只读的、由应用主导的适合提供上下文数据。下一篇我们看Prompts原语它把提示词模板标准化让模型交互可复用。相关推荐MCP三大原语初体验Tools、Resources、Prompts一个都不少资源开发实战文件资源、数据库资源、动态资源Tools原语深度解析从定义到调用全流程