@discourse/mcp fails on subfolder installs β leading-slash paths strip the site subpath
TL;DR
On Discourse instances mounted at a subpath (e.g. https://example.com/forum), @discourse/mcp cannot connect because every internal API call drops the subpath, hitting the wrong host root. This breaks site validation and every subsequent tool call (search, read_topic, etc.), not just --site validation.
Environment
@discourse/mcpv0.2.7 (latest as of writing)- Node via
npx -y @discourse/mcp@latest - Target: Discourse hosted at
https://<host>/forum(subfolder install behind nginx)
Steps to reproduce
- Have a Discourse forum at a subpath, e.g.
https://example.com/forum. Confirmhttps://example.com/forum/about.jsonreturns200. - Run:
npx -y @discourse/mcp@latest --site https://example.com/forum --log_level debug - Send any MCP request (or just observe startup validation).
Expected
Server validates against https://example.com/forum/about.json and tools call https://example.com/forum/<endpoint>.
Actual
Debug log shows the request goes to the host root, not the subpath:
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
The /forum segment is silently dropped.
Root cause
In dist/http/client.js:42 (and the matching write path at :85):
const url = new URL(path, this.base).toString();
Per the WHATWG URL spec, a path argument beginning with / is treated as absolute and replaces the base URLβs pathname. Throughout the codebase, paths are passed with a leading slash, e.g.:
dist/index.js:230βclient.get('/about.json')dist/tools/builtin/select_site.js:15βclient.get('/about.json')- All builtin tools follow the same
'/...'pattern.
Result: new URL('/about.json', 'https://example.com/forum') β https://example.com/about.json. Subfolder is lost on every call.
Suggested fix
Either (a) normalize in the HTTP client by stripping the leading slash from path and ensuring this.base has a trailing slash before resolution:
const base = this.base.toString().replace(/\/?$/, '/');
const rel = path.replace(/^\//, '');
const url = new URL(rel, base).toString();
β¦or (b) change all call sites to use relative paths (no leading slash). Option (a) is safer β single point of change, no behavioural surprises elsewhere.
Workaround for affected users
If your Discourse install also has a subdomain that 301-redirects to the subpath (e.g. forum.example.com β example.com/forum/...), pass the subdomain as --site. The MCP HTTP client follows redirects, so each call resolves to the correct subpath URL and authentication survives the hop. Confirmed working end-to-end (initialize, search, etc.) on v0.2.7.
If no such subdomain exists, there is currently no client-side workaround β the bug needs to be fixed in the package.