MCP bug when subfolder-install in

@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/mcp v0.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

  1. Have a Discourse forum at a subpath, e.g. https://example.com/forum. Confirm https://example.com/forum/about.json returns 200.
  2. Run:
    npx -y @discourse/mcp@latest --site https://example.com/forum --log_level debug
    
  3. 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.

1 Like