Skip to content

fix(routes): return JSON 404 for OAuth discovery probes#636

Open
androidZzT wants to merge 1 commit intoxpzouying:mainfrom
androidZzT:fix/oauth-discovery-noauth
Open

fix(routes): return JSON 404 for OAuth discovery probes#636
androidZzT wants to merge 1 commit intoxpzouying:mainfrom
androidZzT:fix/oauth-discovery-noauth

Conversation

@androidZzT
Copy link
Copy Markdown

Problem

Claude Code's MCP HTTP client probes a number of OAuth .well-known endpoints (per the MCP authorization spec) on every reconnect. When those paths return Gin's default plain-text 404 page not found, the client tries to parse the body as a JSON OAuth error response, fails with a JSON parse error, and ends up stuck in a "needs authentication" state with no OAuth metadata to act on.

The MCP endpoint itself still works, but the auth state machine on the client side never recovers without a process restart. End-user symptoms: the MCP server repeatedly appears as "disconnected" or "needs auth" even though the actual /mcp POST is fine.

Server log captured during one failure

GET /.well-known/oauth-protected-resource/mcp     -> 404
GET /.well-known/oauth-protected-resource         -> 404
GET /.well-known/oauth-authorization-server       -> 404
GET /.well-known/openid-configuration             -> 404
GET /.well-known/oauth-authorization-server/mcp   -> 404
GET /.well-known/openid-configuration/mcp         -> 404
GET /mcp/.well-known/openid-configuration         -> 400
POST /register                                    -> 404
POST /mcp                                         -> 200  (works!)

And the client-side error:

Failed to start OAuth flow for xiaohongshu: SDK auth failed: HTTP 404:
Invalid OAuth error response: SyntaxError: JSON Parse error: Unable to
parse JSON string. Raw body: 404 page not found.

Fix

Register lightweight handlers for the well-known OAuth probe paths and `POST /register` that return a JSON 404 body with a clear `error_description`:

```json
{
"error": "not_found",
"error_description": "This MCP server does not require authentication"
}
```

The client now sees a parseable JSON response, concludes no OAuth metadata is available, and falls back to anonymous mode cleanly. No behavior change for any existing endpoint.

Verification

  • `go build` clean
  • Curl probes return JSON with status 404 and the expected body
  • Repeated reconnects from Claude Code no longer get stuck in the auth-required state

Files

  • `routes.go` (+28 lines)

Claude Code's MCP HTTP client probes a number of OAuth .well-known
endpoints (per the MCP authorization spec) on every reconnect. When
those paths return Gin's default plain-text "404 page not found", the
client tries to parse the body as a JSON OAuth error response, fails
with a JSON parse error, and ends up stuck in a "needs authentication"
state with no OAuth metadata to act on. The MCP endpoint itself still
works, but the auth state machine on the client side never recovers
without a process restart, causing the MCP server to repeatedly appear
as "disconnected" or "needs auth".

Server log captured during one such failure:

  GET /.well-known/oauth-protected-resource/mcp     -> 404
  GET /.well-known/oauth-protected-resource         -> 404
  GET /.well-known/oauth-authorization-server       -> 404
  GET /.well-known/openid-configuration             -> 404
  GET /.well-known/oauth-authorization-server/mcp   -> 404
  GET /.well-known/openid-configuration/mcp         -> 404
  GET /mcp/.well-known/openid-configuration         -> 400
  POST /register                                    -> 404
  POST /mcp                                         -> 200  (works!)

The fix is minimal: register handlers for the well-known OAuth probe
paths and POST /register that return a JSON 404 body with a clear
error_description. The client now sees a parseable JSON response,
concludes no OAuth metadata is available, and falls back to anonymous
mode cleanly.

This is a one-shot fix for a recurring "MCP keeps disconnecting"
problem on the Claude Code side; no behavior change for any existing
endpoint.
@androidZzT androidZzT requested a review from xpzouying as a code owner April 7, 2026 12:06
@kimi-agent-bot
Copy link
Copy Markdown
Contributor

kimi-agent-bot Bot commented Apr 7, 2026

🔍 Kimi Code Agent 正在自动审查此 PR...

@kimi-agent-bot
Copy link
Copy Markdown
Contributor

kimi-agent-bot Bot commented Apr 7, 2026

🔍 Code Review - PR #636

📌 概要

本 PR 修复了 Claude Code MCP 客户端在每次重连时探测 OAuth .well-known 端点,因 Gin 默认返回纯文本 404 page not found 导致客户端 JSON 解析失败,从而卡在"需要认证"状态的问题。

修复方案是为这些 OAuth 探测路径注册处理器,返回 JSON 格式的 404 响应体,让客户端能正确解析并回退到匿名模式。

改动范围routes.go (+28 行),仅新增路由注册,不影响现有端点。


✅ 优点

  1. 问题描述优秀:PR 描述中包含了完整的服务端日志、客户端错误信息、根因分析和验证步骤,非常便于审查
  2. 修改聚焦且风险低:仅 28 行新增代码,不修改任何现有逻辑
  3. 注释清晰:代码中的注释完整解释了"为什么"需要这些路由,而非仅描述"做了什么"
  4. JSON 响应格式合理error + error_description 字段符合 RFC 6749 (OAuth 2.0) 错误响应规范,客户端可以正确解析

⚠️ 潜在问题与建议

1. 🔴 路由冲突风险:/mcp/.well-known/*path vs router.Any("/mcp/*path")

这是本次 review 最主要的关注点。

现有代码中已注册:

router.Any("/mcp/*path", gin.WrapH(mcpHandler))

PR 新增了:

router.GET("/mcp/.well-known/*path", noAuthRequired)

router.Any 会为 所有 HTTP 方法(包括 GET)注册 /mcp/*path。在 Gin 的路由树(基于 httprouter)中,catch-all 通配符 *path 和同级的固定路径段 .well-known 在同一 HTTP 方法树中可能存在冲突。

虽然 PR 作者提到 go build 和 curl 测试通过,但建议:

  • 确认在 Gin v1.10.1 下 启动时不会 panic(httprouter 路由冲突通常在注册时 panic)
  • 添加对应的单元测试或集成测试覆盖这些路由,确保回归安全

2. 🟡 建议考虑使用 NoRoute 替代方案

Gin 提供了 NoRoute handler,可以统一处理所有未匹配路由的 404 响应。这种方式可以一次性解决问题,无需逐个注册路由,也完全避免了上述路由冲突风险:

router.NoRoute(func(c *gin.Context) {
    c.JSON(http.StatusNotFound, gin.H{
        "error":             "not_found",
        "error_description": "The requested resource was not found",
    })
})

优势

  • 无路由冲突风险
  • 自动覆盖未来可能新增的 OAuth 探测路径(MCP 规范演进时无需改代码)
  • 所有未匹配路由都返回 JSON 格式,整体 API 风格更一致
  • 代码更简洁(~5 行 vs ~15 行路由注册)

注意:如果项目有其他场景期望纯文本 404(如 HTML 页面),则需要在 NoRoute 中根据 Accept header 做区分。但从当前代码看,本项目是纯 API 服务,JSON 404 应该是合理的默认行为。

3. 🟡 POST /register 路径较为通用

/register 是一个比较常见的路径名。虽然目前项目中不存在冲突,但如果未来添加用户注册等功能,可能需要调整。如果采用上面的 NoRoute 方案则无此顾虑。

4. 🟢 注释风格小建议

项目现有注释以中文为主(如 // 健康检查// MCP 端点// 添加中间件),而本次新增注释为英文。建议保持风格一致,或者至少在关键注释处附上中文说明。这是一个非常小的建议,不影响合并。


📋 总结

维度 评价
问题真实性 ✅ 真实且影响用户体验
修复方向 ✅ 正确
代码质量 ✅ 良好
路由冲突风险 ⚠️ 需确认
测试覆盖 ⚠️ 未添加测试
可维护性 🟡 建议考虑 NoRoute 方案

总体评价:PR 解决了一个实际且影响用户体验的问题,修复方向正确,代码质量良好。主要建议是确认 /mcp/.well-known/*path 与现有 router.Any("/mcp/*path") 的路由冲突问题,并强烈建议考虑使用 NoRoute 作为更简洁、更安全的替代方案。如果路由冲突问题确认不存在,当前实现也是可以接受的。


🤖 本评论由 Kimi Code Agent 自动生成

@kimi-agent-bot
Copy link
Copy Markdown
Contributor

kimi-agent-bot Bot commented Apr 7, 2026

现在我已经收集了所有需要的信息,让我撰写审查意见并发布。

已完成对 PR #636 的代码审查,评论已发布:查看评论

审查摘要

PR 背景:Claude Code 的 MCP 客户端在重连时探测 OAuth .well-known 端点,Gin 默认返回纯文本 404 导致客户端 JSON 解析失败,卡在"需要认证"状态。修复方案是为这些路径注册 JSON 格式的 404 响应处理器。

主要发现

  1. 🔴 路由冲突风险:新增的 router.GET("/mcp/.well-known/*path") 与现有的 router.Any("/mcp/*path") 在 Gin 的路由树中可能存在 catch-all 通配符冲突,需要确认不会导致启动时 panic。

  2. 🟡 建议使用 NoRoute 替代方案:Gin 提供的 NoRoute handler 可以一次性将所有未匹配路由返回 JSON 格式 404,更简洁、无冲突风险,且自动覆盖未来可能新增的 OAuth 探测路径。

  3. 🟡 POST /register 路径通用性:该路径未来可能与用户注册功能冲突。

  4. 🟢 注释风格:项目现有注释以中文为主,新增注释为英文,建议保持一致。

总体评价:问题真实、修复方向正确、代码质量良好,建议优先确认路由冲突问题并考虑 NoRoute 方案。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant