MCP 在子文件夹安装时出现 bug

@discourse/mcp 在子目录安装中失败 —— 前导斜杠路径会剥离站点子路径

简要说明

在挂载于子路径的 Discourse 实例上(例如 https://example.com/forum),@discourse/mcp 无法连接,因为所有内部 API 调用都会丢失子路径,导致请求错误的根路径。这不仅破坏了站点验证,还会导致所有后续工具调用(如 searchread_topic 等)失败,而不仅仅是 --site 验证。

环境信息

  • @discourse/mcp v0.2.7(截至撰写时的最新版本)
  • 通过 npx -y @discourse/mcp@latest 运行 Node
  • 目标:托管在 https://<host>/forum 的 Discourse(nginx 后端的子目录安装)

复现步骤

  1. 确保存在一个位于子路径的 Discourse 论坛,例如 https://example.com/forum。确认访问 https://example.com/forum/about.json 返回 200
  2. 运行以下命令:
    npx -y @discourse/mcp@latest --site https://example.com/forum --log_level debug
    
  3. 发送任意 MCP 请求(或仅观察启动时的验证过程)。

预期结果

服务器应针对 https://example.com/forum/about.json 进行验证,且工具调用应指向 https://example.com/forum/<endpoint>

实际结果

调试日志显示请求被发送至主机根路径,而非子路径:

DEBUG HTTP GET https://example.com/about.json
DEBUG HTTP GET https://example.com/about.json -> 403 Forbidden
ERROR Failed to validate --site https://example.com/forum: HTTP 403 Forbidden

/forum 段被静默丢弃。

根本原因

dist/http/client.js:42(以及对应的写入路径 :85)中:

const url = new URL(path, this.base).toString();

根据 WHATWG URL 规范,以 / 开头的 path 参数被视为绝对路径,会替换基础 URL 的路径名。在整个代码库中,路径均以带前导斜杠的形式传递,例如:

  • dist/index.js:230client.get('/about.json')
  • dist/tools/builtin/select_site.js:15client.get('/about.json')
  • 所有内置工具均采用相同的 '/...' 模式。

结果:new URL('/about.json', 'https://example.com/forum') 会生成 https://example.com/about.json。每次调用都会丢失子文件夹路径。

建议修复方案

方案 (a):在 HTTP 客户端中规范化处理,移除 path 的前导斜杠,并确保在解析前 this.base 包含尾随斜杠:

const base = this.base.toString().replace(/\/?$/, '/');
const rel  = path.replace(/^\//, '');
const url  = new URL(rel, base).toString();

或者方案 (b):将所有调用点改为使用相对路径(不带前导斜杠)。方案 (a) 更为安全——只需修改单点,不会引起其他地方的行为异常。

受影响用户的临时解决方案

如果您的 Discourse 安装还拥有一个会 301 重定向到子路径的子域名(例如 forum.example.comexample.com/forum/...),可以将该子域名作为 --site 参数传入。MCP HTTP 客户端会跟随重定向,因此每次调用都能解析到正确的子路径 URL,且认证信息在跳转过程中得以保留。已在 v0.2.7 上确认端到端可用(包括 initializesearch 等)。

如果不存在此类子域名,则目前尚无客户端侧的临时解决方案——该缺陷必须在包层面进行修复。

1 个赞