MCP tool provider authorization returns an opaque 500 when the MCP server cannot be reached

这个报错通常出现在 Dify 自托管环境中,当你点击 MCP tool provider 的 Authorize (或创建后自动触发授权)时,后端在连接 MCP server 的阶段抛出了未被正确转换的上游 httpx 异常,最终被通用错误处理器统一包装成不透明的 500,因此前端只看到 500 I

快速结论:这个报错通常出现在 Dify 自托管环境中,当你点击 MCP tool provider 的 Authorize(或创建后自动触发授权)时,后端在连接 MCP server 的阶段抛出了未被正确转换的上游 httpx 异常,最终被通用错误处理器统一包装成不透明的 500,因此前端只看到 500 Internal Server Error,拿不到真实原因。优先排查 MCP server 是否可达、URL 路径是否以 /mcp/sse 结尾。

适用环境:Dify 1.17.1(1.17.0 及 main @ a3b2786b32 也复现),Self Hosted(Docker);错误发生在授权 MCP tool provider 时,与具体 MCP server 实现相关。

最快修复方案:暂无确认的一步修复方案。Issue 讨论确认当前 main 上没有 PR 端到端修复这个 500-vs-4xx 行为,此前两个尝试(PR #40111 已关闭未合并、PR #39321 仍开放未合并)只覆盖了部分路径,因此官方层面还没有直接可用的补丁。

注意事项:该 500 与 #41109 / #41328 / #42061 中 `provider_id` / `server_identifier` 混用导致的 UUID cast 失败(已由 #41360 修复)不是同一个问题——本例中 provider_id 是合法 UUID,provider 记录能被查到,失败发生在后续连接 MCP server 时。若依赖 #40111 思路自行修改,需注意它未合并,且未覆盖下文列出的两处异常泄漏点,改动前应自行验证。

问题场景

在 Dify Self Hosted(Docker)中注册一个 MCP tool provider 后,点击 MCP 详情面板里的 Authorize 按钮(控制台也会在创建后自动触发一次授权)。前端会向 /console/api/workspaces/current/tool-provider/mcp/auth 发送带 provider_id 的请求。当该 MCP server 不可达,或其 URL 路径不以 /mcp/sse 结尾(例如 n8n 风格的 https://example.com/mcp/<id> 端点)时,接口返回 500。

报错原文

{"message":"Internal Server Error","code":"unknown","status":500}

触发请求示例:

curl 'http://localhost/console/api/workspaces/current/tool-provider/mcp/auth' \
  -H 'content-type: application/json' \
  --data-raw '{"provider_id":"5f1d2cf3-53ca-41ca-af0b-e3df05f78c1d"}'

原因分析

根据 Issue 正文与讨论,根因是 ToolMCPAuthApi.post(以及同类的 console MCP 端点)只把 MCPError / ValueError 转换成带原因的 4xx,其余异常全部落到通用 500 处理器。有两条路径会让原始 httpx 异常穿透这个过滤器:

  • 路径一:core/mcp/client/sse_client.py 在捕获异常后直接 raise,未做转换,导致 httpx.ConnectError / RemoteProtocolError / ReadTimeout 逃逸。这同时破坏了 MCPClient._initialize 里的 transport fallback——该 fallback 只捕获 MCPConnectionError / ValueError,所以对 URL 不以 /mcp 结尾的 streamable-HTTP server,SSE 探测阶段的传输错误会让 mcp transport 根本没有机会被尝试。
  • 路径二:core/mcp/auth/auth_flow.py::register_client 在动态客户端注册被拒绝时调用 response.raise_for_status()auth() 只转换 RequestError,端点内层处理器只捕获 MCPRefreshTokenError,因此 httpx.HTTPStatusError 同样以 500 逃逸。

讨论中还提到两个未能修复该问题的历史尝试:PR #40111(针对 #40001,实现了“优先 streamable-http 并捕获 httpx.TransportError 以便 SSE fallback 生效”的思路)已于 2026-08-18 被作者关闭且未合并;PR #39321(针对 #39301)是同类 fallback 修复的更早版本,目前仍开放未合并。两者都未触及以上第二处泄漏路径。

环境排查

  • 确认 Dify 版本:1.17.1 / 1.17.0 / main @ a3b2786b32 均已复现。
  • 确认部署方式:Self Hosted(Docker)。
  • 确认 MCP server 是否可从 Dify 容器网络内访问(连通性、DNS、端口、防火墙)。
  • 确认 MCP server URL 路径是否以 /mcp/sse 结尾;n8n 风格的 https://example.com/mcp/<id> 是已知触发场景。
  • 确认 provider_id 是合法 UUID,以排除 #41109 / #41328 / #42061 描述的 ::uuid cast 问题(该问题已由 #41360 修复)。

解决步骤

  1. 先确认失败位置:用 Issue 提供的无控制台最小复现,直接构造 MCPClient 指向不可达地址(如 http://127.0.0.1:9/foo),观察抛出的异常类型是否为原始 httpx 异常而非 MCPError
    from core.mcp.error import MCPError
    from core.mcp.mcp_client import MCPClient
    
    try:
        with MCPClient(server_url="http://127.0.0.1:9/foo", headers={}, timeout=3, sse_read_timeout=5):
            pass
    except BaseException as e:
  2. 核对 MCP server 的真实可达性与 URL 形态。若 URL 不以 /mcp/sse 结尾,属于 Issue 明确点名的触发条件,可优先尝试将其改为标准路径或确保服务端在该路径上响应。
  3. 在官方修复落地前,可优先尝试讨论中提出的补丁方向(此为推测方案,尚未合并验证):把 sse_client.py 的 catch-all 与 register_client 中的 raise_for_status() 统一包装成 MCPConnectionError / ValueError,使其落入 except (MCPError, ValueError) 分支并返回带原因的 4xx;同时按 PR #40111 思路调整 MCPClient._initialize,捕获 httpx.TransportError 让 transport fallback 真正生效。
  4. 自行修改后重新构建并重启 API 服务,再回到 MCP 详情面板点击 Authorize

验证方法

修复生效后,相同的授权请求不应再返回不透明的 {"message":"Internal Server Error","code":"unknown","status":500}。在 MCP server 不可达的场景下,预期应收到携带具体连接失败原因的 4xx 响应;在 URL 不以 /mcp 结尾的 streamable-HTTP 场景下,预期 SSE 探测失败后能自动回退到 mcp transport。若仍返回 500,说明上述两处异常泄漏点未被完全覆盖。

参考来源

langgenius/dify #42176

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 22808

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注