@discourse/mcp 在子目录安装中失败 —— 前导斜杠路径会剥离站点子路径
简要说明
在挂载于子路径的 Discourse 实例上(例如 https://example.com/forum),@discourse/mcp 无法连接,因为所有内部 API 调用都会丢失子路径,导致请求错误的根路径。这不仅破坏了站点验证,还会导致所有后续工具调用(如 search、read_topic 等)失败,而不仅仅是 --site 验证。
环境信息
@discourse/mcpv0.2.7(截至撰写时的最新版本)- 通过
npx -y @discourse/mcp@latest运行 Node - 目标:托管在
https://<host>/forum的 Discourse(nginx 后端的子目录安装)
复现步骤
- 确保存在一个位于子路径的 Discourse 论坛,例如
https://example.com/forum。确认访问https://example.com/forum/about.json返回200。 - 运行以下命令:
npx -y @discourse/mcp@latest --site https://example.com/forum --log_level debug - 发送任意 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:230—client.get('/about.json')dist/tools/builtin/select_site.js:15—client.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.com → example.com/forum/...),可以将该子域名作为 --site 参数传入。MCP HTTP 客户端会跟随重定向,因此每次调用都能解析到正确的子路径 URL,且认证信息在跳转过程中得以保留。已在 v0.2.7 上确认端到端可用(包括 initialize、search 等)。
如果不存在此类子域名,则目前尚无客户端侧的临时解决方案——该缺陷必须在包层面进行修复。