快速结论:这个报错出现在 MCP Python SDK 的资源校验函数 check_resource_allowed 上:它只做字符串前缀比较,没有先归一化路径,导致 check_resource_allowed treats /api/../admin as under /api。优先排查请求 URL 路径中是否包含 .、.. 或 %2e%2e 这类点段。
适用环境:Issue 中确认的环境为 MCP Python SDK,分支/提交 main @ 08a3bc8,涉及 mcp.shared.auth_utils 与 tests/shared/test_auth_utils.py。Issue 未提供操作系统、Python 版本、CUDA、显卡或其它依赖版本信息。
最快修复方案:暂无已合并的确认修复;该 Issue 被维护者以重复为由关闭,并指向 #3303 跟踪。评论中提到的可优先尝试方案是:在校验前对 requested_path 与 configured_path 做一次 urllib.parse.unquote() 百分号解码,再用 posixpath.normpath() 归一化(保证以 / 开头),然后重新应用尾斜杠归一化,最后保留现有 startswith 前缀规则。
注意事项:上述修复思路来自 Issue 评论中的本地实现与回归测试,并非 Issue 中已合并的官方修复。归一化必须同时作用于请求路径和配置路径,否则 /api123 对 /api 的路径边界防护可能被破坏。百分号解码只做一次,避免双重解码引入新的绕过面。是否采纳以 #3303 的最终处理为准。
问题场景
使用者调用 MCP Python SDK 的 check_resource_allowed,用配置的 https://example.com/api 校验请求资源 https://example.com/api/../admin 时,函数返回 True,即把已经走出 /api 前缀的路径当成仍在 /api 下。同类情况还包括请求 https://example.com/mcp/.. 对配置 https://example.com/mcp,以及百分号编码版本 https://example.com/api/%2e%2e/admin,均返回 True。该问题影响基于路径前缀的资源访问边界判断。
报错原文
check_resource_allowed treats /api/../admin as under /api
check_resource_allowed("https://example.com/api/../admin", "https://example.com/api")
# True today. I expected False.
requested `https://example.com/mcp/..` vs configured `https://example.com/mcp`
requested `https://example.com/api/%2e%2e/admin` vs configured `https://example.com/api`
原因分析
最可能的原因是 check_resource_allowed 在补齐尾斜杠后,直接使用 requested_path.startswith(configured_path) 做前缀比较,没有先折叠 . 和 ..。评论中的复现说明:尾斜杠补齐会把 /api/../admin 变成 /api/../admin/,该字符串仍以 /api/ 开头,因此前缀检查通过,但语义上 /api/../admin 实际解析为 /admin,已经越出 /api 前缀。百分号编码的 %2e%2e / %2E%2E 同样不会被前缀检查识别。
环境排查
- 确认使用的 MCP Python SDK 是否处于
main@08a3bc8或包含该行为的版本。 - 确认问题入口为
mcp.shared.auth_utils.check_resource_allowed。 - 检查调用方传入的资源 URL 路径是否可能包含
.、..、%2e、%2e%2e。 - 运行
tests/shared/test_auth_utils.py,确认现有路径边界用例(如/api123对/api)是否仍然通过。 - Issue 未提供 Python、操作系统、依赖版本信息,无需按版本号排查。
解决步骤
- 在本地复现:调用
check_resource_allowed("https://example.com/api/../admin", "https://example.com/api"),确认当前返回True。 - 确认跟踪状态:该 Issue 已被维护者标记为 #3303 的重复项并关闭,修复应跟随 #3303。
- 若需要自行修补,可优先尝试评论中给出的归一化方案:对
requested_path和configured_path先执行一次urllib.parse.unquote()百分号解码,再执行posixpath.normpath()并保证结果以/开头。 - 重新应用尾斜杠归一化,保持
/foo与/foo/等价,确保/api123不会被/api前缀误匹配。 - 在归一化之后,再执行原有的
requested_path.startswith(configured_path)判断。 - 补充回归用例,至少覆盖:越界点段(
..、%2e%2e、../../admin、/mcp/..),以及归一化后仍合法停留在前缀内的路径(/api/v1/../v2、/api/./v1)。 - 运行
tests/shared/test_auth_utils.py,确认 16 个既有 auth 测试仍全部通过。
验证方法
修补后再次调用 check_resource_allowed("https://example.com/api/../admin", "https://example.com/api"),应返回 False;/mcp/.. 与 /api/%2e%2e/admin 对各自配置前缀也应返回 False。同时确认归一化后仍处于前缀内的 /api/foo/../v1 对 /api 仍返回 True,/api123 对 /api 仍被拒绝,且 tests/shared/test_auth_utils.py 全部通过。
参考来源
modelcontextprotocol/python-sdk #3464
modelcontextprotocol/python-sdk #3303
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[Bug] Agent custom user tools are permanently removed during cold start while the installed-plugin catalog is loading](https://www.chat-gpts.plus/wp-content/uploads/2026/10/18856-ce9d4c32-768x403.jpg)

