#2554·mcp-use

cli: two different basePath join rules, so basePath "/" makes the inspector mount and dev-API routes unreachable

Author: aishwary-dongreCreated Sep 15, 2026Updated Sep 15, 2026
LabelsbugduplicateTypeScriptserver

Summary

The CLI carries two different rules for joining a child path onto a server basePath, and they disagree when basePath is exactly "/".

src/views/document.ts:3-6 handles the root explicitly:

typescript
function pathUnderBase(basePath: string, childPath: string): string {
  const child = childPath.replace(/^\/+/, "");
  return basePath === "/" ? `/${child}` : `${basePath}/${child}`;
}

src/cli/inspector-route.ts:16 and src/cli/dev-api.ts:72-74 interpolate directly instead:

typescript
  const inspectorPath = `${basePath}/inspector`;
typescript
    const infoPath = `${basePath}/inspector/api/dev/info`;
    const startPath = `${basePath}/inspector/api/dev/start-tunnel`;
    const stopPath = `${basePath}/inspector/api/dev/stop-tunnel`;

With basePath === "/" those produce "//inspector" and "//inspector/api/dev/info". View assets resolve correctly at the root while inspector routes do not, from the same basePath value in the same package.

The Problem

  1. basePath: "/" is valid, supported configuration. assertServerConfig (packages/server/src/config.ts:276-287) rejects a trailing slash only when the path is longer than one character, so "/" passes:

    typescript
    (basePath.length > 1 && basePath.endsWith("/"))

    normalizePathname has an explicit if (pathname === "/") return "/" fast path, and fetch-app.ts, branding.ts and server.ts all branch on basePath === "/". src/cli/dev.ts:610 reads it straight through: basePath = server.basePath ?? "/mcp".

  2. mcp-use start --with-inspector advertises a URL that does not reach the Inspector. src/bin/main.ts:295 builds the printed URL by stripping a trailing slash and appending:

    typescript
    `mcp-use inspector at ${started.url.replace(/\/$/, "")}/inspector`

    That normalises to a single slash, http://localhost:3000/inspector. The mount guard at src/bin/start.ts:247 uses isInspectorRequest, which requires "//inspector". The advertised path therefore falls through next() to the MCP handler. Verified directly:

    isInspectorPath("/inspector", "/")   // false
    isInspectorPath("/inspector", "/mcp") // false  (correct)
    viewAssetsBasePath("/", "widget")     // "/_mcp-use/views/widget/"  (correct)
  3. The dev API routes are unreachable at the root. Exercising the real handler with getBasePath: () => "/" and a sentinel fallback:

    GET http://127.0.0.1:4242/inspector/api/dev/info
    -> 418 "FELL THROUGH TO FALLBACK"   (expected 200 dev info)

    So the Inspector UI cannot read fromCli, and the tunnel start/stop buttons POST into the MCP handler. Nothing is logged.

To be accurate about scope: mcp-use dev is not broken today. src/cli/dev.ts:995 and :1034 print and open `${basePath}/inspector`, the same doubled form the guard matches, so dev is self-consistently //inspector — a malformed-looking URL that happens to work. This is worth stating because it means a fix must move those two sites together with the predicate, or dev breaks.

Reproduced on main at dcaa0b8.

Test coverage: none. Every basePath literal in tests/ is "/mcp" or "/api/mcp" (tests/cli/dev-api.test.ts:39,64, tests/cli/dev.test.ts:190,217,386,454,478,866). There is no test for isInspectorPath at all.

Proposed fix

Give the CLI one join rule. Move pathUnderBase out of views/document.ts into a small internal module and route every consumer through it: the predicate in inspector-route.ts, the three routes in dev-api.ts, and the two URL-building sites in dev.ts:995 and dev.ts:1034.

typescript
export function pathUnderBase(basePath: string, childPath: string): string {
  const child = childPath.replace(/^\/+/, "");
  return basePath === "/" ? `/${child}` : `${basePath}/${child}`;
}

All five inspector sites then agree on "/inspector" at the root, which is what start already advertises, and dev prints a clean URL instead of a doubled one. Nested basePath values are unaffected, since the helper's non-root branch is the current expression.

Two smaller options exist if you would rather not add a module: duplicate the ternary at each site, which triplicates the rule the issue is about, or normalise basePath once at the point it is read in dev.ts:610 and start.ts:236, which leaves the two naive joins as traps for the next caller. I went with the shared helper, but happy to follow your preference.