FastMCP v3 Resource 内部类型重构:ResourceResult / ResourceContent 严格类型化的原理与迁移实战 FastMCP v3 Resource 内部类型重构ResourceResult / ResourceContent 严格类型化的原理与迁移实战【免费下载链接】fastmcp The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp本指南基于 FastMCP 3.0.0 的破坏性变更说明见 dev-docs/v3-notes/resource-internal-types.md系统讲解资源Resource读写链路中ResourceResult与ResourceContent两个内部类型的严格类型约束。文章覆盖 v2.x 旧自动转换行为的问题、v3.0 的新类型签名、底层序列化实现原理、完整迁移示例并结合仓库源码与测试用例给出可验证的运行时行为边界帮助你在升级 v3 时一次性消除资源返回类型的隐式错误。变更背景为什么 v3 要收紧资源返回类型在 v2.x 时代资源函数可以自由返回dict、list框架会自动将其序列化为 JSON 或拆分为多个内容条目。这种便利掩盖了一个致命歧义客户端读到的是一个 JSON 数组字符串还是两个独立的内容条目# v2.x 旧行为——静默歧义 return [item1, item2] # 客户端看到 2 个条目还是一个 JSON 数组无法确定只有当客户端真正读取资源时问题才会暴露——错误被推迟到运行时且难以排查。v3.0 的核心目标是把这类错误从客户端读取时才暴露提前到开发期由类型检查器立即捕获详见 resource-internal-types.md 的 Summary 与 Why This Change 章节。新的类型约束三个核心签名Resource.read() 的返回类型v3 中资源读取的合法返回类型被严格限定为str | bytes | ResourceResultreturn text content—— 合法文本内容return bbinary data—— 合法二进制内容return ResourceResult([ResourceContent(...)])—— 合法完全显式的多条目/自定义 MIME 响应return {key: value}—— 非法应改用json.dumps()显式序列化return [item1, item2]—— 非法应改用ResourceResult([ResourceContent(...)])return ResourceContent(...)—— 非法单个条目也必须放进列表中。该签名在源码中有明确体现base.py 中Resource.read()的注解为- str | bytes | ResourceResultFunctionResource.read()见 function_resource.py同样遵循此契约。ResourceResult 的类型签名ResourceResult( contents: str | bytes | list[ResourceContent], meta: dict[str, Any] | None None )合法用法ResourceResult(plain text)—— 自动包装为单个文本条目ResourceResult(bbinary)—— 自动包装为单个二进制条目ResourceResult([ResourceContent(...), ResourceContent(...)])—— 多条目显式控制每个条目的 MIME 类型与元数据。非法用法运行时抛TypeError或由类型检查器标记ResourceResult({key: value})—— dict 不应直接传入详见下文运行时行为边界ResourceResult([a, b])—— 裸 list 不被支持列表中的元素必须是ResourceContentResourceResult(resource_content_obj)—— 单个条目必须放进列表中不能直接传对象。ResourceContent 的类型签名ResourceContent( content: Any, # 非 str/bytes 会自动 JSON 序列化 mime_type: str | None None, meta: dict[str, Any] | None None )ResourceContent.__init__的自动序列化规则见 base.py 的ResourceContent.__init__实现传入 content 类型存储结果默认 mime_typestr原样透传text/plainbytes原样透传application/octet-streamdictJSON 序列化字符串application/jsonlistJSON 序列化字符串application/jsonBaseModelPydantic 模型JSON 序列化字符串application/json需要注意的是ResourceContent对content字段宽进严出——任何 Python 对象都能进入构造函数并被序列化而ResourceResult只接受str | bytes | list[ResourceContent]。这就是每个条目都能自动序列化但条目集合必须是显式ResourceContent列表的设计意图。运行时行为边界源码揭示的精确语义严格类型化主要体现在类型检查器层面函数返回注解str | bytes | ResourceResult在运行时层面FastMCP 仍然保留了一定的自动序列化兜底。结合 base.py 的convert_raw_to_resource_result()与ResourceResult._normalize_contents()可以梳理出精确的行为边界1.mcp.resource装饰的函数返回 JSON-native 类型dict / list / tuple / int / float / bool / None时运行时仍会被自动 JSON 序列化不会抛错。convert_raw_to_resource_result()对这类值执行json.dumps()并包装为mime_typeapplication/json的条目。这一点在测试 tests/resources/test_function_resources.py 的test_dict_return_auto_serializes中有直接验证_read()会将{key: value}自动序列化为 JSON 文本。2. 但裸 list 的语义已经改变在ResourceResult._normalize_contents()中list分支会逐个校验元素是否为ResourceContent若不是则抛出带索引提示的TypeError如contents[0] must be ResourceContent, got str。所以ResourceResult([a, b])在运行时确实会抛TypeError而return [item1, item2]这种裸 list 返回则依赖json.dumps()兜底成单个 JSON 条目——两种写法的产物完全不同这正是 v3 要求你显式表达意图的原因。3. BaseModel 等自定义类型会抛TypeErrorconvert_raw_to_resource_result()对不属于 JSON-native 的类型会 fall through 到ResourceResult(raw_value)而ResourceResult的规范化逻辑最终抛出TypeError。测试test_basemodel_return_raises_type_error与test_custom_type_return_raises_type_error均验证了这一点——返回 Pydantic 模型时必须显式构造ResourceResult([ResourceContent(...)])不能依赖隐式转换。4. 直接返回ResourceContent也会被拒绝文档明确要求ResourceContent(...)必须放入列表中因为ResourceResult不接受单条目对象直传。迁移指南三类典型场景的升级写法场景一返回 JSON 数据dict → 显式序列化升级前def get_config() - dict: return {key: value, nested: {a: 1}}升级后import json def get_config() - str: return json.dumps({key: value, nested: {a: 1}})场景二返回多个条目list → 二选一升级前def get_items() - list: return [user1, user2, user3]升级后——方案 1单个 JSON 数组保留一个资源一条内容语义import json def get_items() - str: return json.dumps([user1, user2, user3])升级后——方案 2多个独立内容条目每个条目可携带独立 MIME 与元数据from fastmcp.resources import ResourceContent, ResourceResult def get_items() - ResourceResult: return ResourceResult([ ResourceContent(user1), ResourceContent(user2), ResourceContent(user3), ])场景三返回结构化数据并指定自定义 MIME升级前def get_html() - dict: return {html: divcontent/div}升级后from fastmcp.resources import ResourceContent, ResourceResult def get_html() - ResourceResult: return ResourceResult([ ResourceContent( contentdivcontent/div, mime_typetext/html ) ])注意这里ResourceResult的contents列表支持每个条目独立的mime_type与meta而ResourceResult本身还带有结果级meta如meta{count: 2}两者会在resources/read响应中分别送达客户端。底层实现解读从 Python 对象到 MCP 线上的转换链理解 v3 的类型设计需要看完整的数据流。核心链路在 base.py 中实现第一步用户函数返回值 →ResourceResult。服务端通过Resource.convert_result()调用convert_raw_to_resource_result()ResourceResult原样透传str/bytes包装为带组件声明 MIME 的单条目JSON-native 类型自动json.dumps()其余类型交给ResourceResult规范化抛错或包装。第二步ResourceResult→ MCP 线上类型。ResourceResult.to_mcp_result(uri)遍历contents对每个ResourceContent调用to_mcp_resource_contents(uri)str内容 →mcp_types.TextResourceContents携带uri、text、mime_type、_metabytes内容 →mcp_types.BlobResourceContents二进制经base64.b64encode编码后放入blob字段。测试 tests/resources/test_resources.py 的test_to_mcp_blob_contents验证了b\x00\x01\x02会被编码为 base64 字符串AAEC。第三步服务端分发。resources/read请求在 mcp_operations.py 的_on_read_resource中调用self.read_resource(...)随后经中间件链缓存、鉴权、计时等均有on_read_resource钩子见 middleware/middleware.py完成响应。ResourceResult作为统一返回类型贯穿全链路。此外Resource组件本身还带有声明级配置mime_type默认text/plain、meta、annotations、auth等。声明为text/csv的资源在读取时不会回退成text/plain——convert_raw_to_resource_result会把组件声明的 MIME 类型透传给内容条目见 base.py 中convert_raw_to_resource_result的 docstring。类型检查器如何拦截错误v3 的类型契约使类型检查器mypy / pyright 等能在开发期直接报错mcp.resource(data://test) def bad_resource() - dict: # ← 类型错误应为 str | bytes | ResourceResult return {key: value}当函数注解为dict而实际返回dict时静态分析会标记返回类型与Resource.read()契约不匹配。这是有意设计类型系统在开发期强制正确类型而不是把错误留给客户端读取时才发现原文 Type Checking 章节明确阐述了这一点。建议在 CI 中加入类型检查步骤把这类错误挡在合并之前。兼容性与升级影响这是一次破坏性变更Breaking Change适用前提如下如果你的资源函数仍然返回dict或list且忽略类型警告——代码可能通过类型检查但在运行时客户端读取资源时行为已与 v2 不同dict 仍会被 JSON 序列化兜底裸 list 则会在规范化时抛TypeError返回BaseModel或自定义类型会抛TypeError必须迁移为显式ResourceResult([ResourceContent(...)])最稳妥的迁移路径是所有结构化数据统一走json.dumps()或ResourceResult([ResourceContent(...)])让意图在源码层面一目了然。测试验证用仓库测试用例巩固迁移信心仓库为上述行为提供了完整测试覆盖可作为迁移后的回归依据tests/resources/test_resources.py覆盖ResourceContent的字符串/二进制/dict/list/BaseModel 序列化与默认 MIME 推断TestResourceContent、ResourceResult的多种初始化与错误分支TestResourceResult含test_init_from_mixed_list_raises_type_error、meta在结果级与条目级的双向传播TestResourceMetaPropagationtests/resources/test_function_resources.py覆盖FunctionResource.read()原始返回与_read()转换的一致性、dict 自动序列化、BaseModel/自定义类型抛TypeError、同步函数在线程池中并发执行等行为。小结FastMCP v3 的 Resource 内部类型重构本质上是把隐式便利替换为显式契约str | bytes | ResourceResult三个合法返回类型、ResourceResult(contents: str | bytes | list[ResourceContent])的规范化规则、ResourceContent对任意内容的自动 JSON 序列化共同构成了一条清晰、可静态检查的资源数据通路。升级时只需遵循JSON 数据用json.dumps()、多条目用ResourceResult([ResourceContent(...)])、自定义 MIME 在条目上声明三条原则即可在开发期消除资源返回类型的一切歧义。【免费下载链接】fastmcp The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考