feat: port CLI, TUI, and launcher from v1.5 into v2 (#1452)

Ports the CLI, TUI, and launcher from v1.5 into v2 behind a single
`mcp-inspector` entrypoint (launcher → web/cli/tui). Includes the
`mcp-inspector --web --dev` launcher fix (Vite alias resolution + prod-server
double-start), the root `postinstall` install cascade with simplified CI, and
spec/AGENTS docs (no-workspaces rationale, Development setup).

Closes #1246.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bob Dickinson 2026-06-14 17:27:49 -07:00 • committed by GitHub
commit 2488ca5e38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 25594 additions and 236 deletions

View file

@ -0,0 +1,64 @@
#!/usr/bin/env node
import { dirname, join } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { Command } from "commander";
import { parseLauncherArgv } from "./parse-launcher-argv.js";
const launcherDir = dirname(fileURLToPath(import.meta.url));
function clientEntry(client: "web" | "cli" | "tui"): string {
return pathToFileURL(
join(launcherDir, "..", "..", client, "build", "index.js"),
).href;
}
const program = new Command();
program
.name("mcp-inspector")
.description("MCP Inspector – run web UI, CLI, or TUI")
.option("--web", "Run web UI (default)")
.option("--cli", "Run CLI")
.option("--tui", "Run TUI");
let parsedArgv;
try {
parsedArgv = parseLauncherArgv(process.argv);
} catch (err) {
const message =
err instanceof Error ? err.message : "Invalid launcher arguments.";
console.error(`Error: ${message}`);
process.exit(1);
}
const { mode, forwardedArgv, hasPrefixModeFlag } = parsedArgv;
const helpOnly =
process.argv.includes("-h") || process.argv.includes("--help");
if (helpOnly && !hasPrefixModeFlag) {
program.outputHelp();
console.log(
"\nMode flags (--web, --cli, --tui) must appear before app options. All following arguments are forwarded unchanged.",
);
process.exit(0);
}
async function run(): Promise<void> {
if (mode === "web") {
const { runWeb } = await import(clientEntry("web"));
await runWeb(forwardedArgv);
} else if (mode === "cli") {
const { runCli } = await import(clientEntry("cli"));
await runCli(forwardedArgv);
} else {
const { runTui } = await import(clientEntry("tui"));
await runTui(forwardedArgv);
}
}
run().catch((err: unknown) => {
console.error("Error running MCP Inspector:", err);
process.exit(1);
});

View file

@ -0,0 +1,43 @@
export const LAUNCHER_MODE_FLAGS = new Set(["--web", "--cli", "--tui"]);
export type LauncherMode = "web" | "cli" | "tui";
export type ParseLauncherArgvResult = {
mode: LauncherMode;
forwardedArgv: string[];
hasPrefixModeFlag: boolean;
};
/**
* Launcher mode flags are read only from a contiguous prefix of user args
* (argv[2…]). Everything after the first non-mode token is forwarded
* unchanged so values like `--cli` can appear in app/server config.
*/
export function parseLauncherArgv(argv: string[]): ParseLauncherArgvResult {
const executable = argv.slice(0, 2);
const userArgs = argv.slice(2);
const prefixFlags: string[] = [];
let index = 0;
while (index < userArgs.length && LAUNCHER_MODE_FLAGS.has(userArgs[index]!)) {
prefixFlags.push(userArgs[index]!);
index++;
}
if (prefixFlags.length > 1) {
throw new Error("Specify at most one of --web, --cli, or --tui.");
}
const mode: LauncherMode =
prefixFlags[0] === "--tui"
? "tui"
: prefixFlags[0] === "--cli"
? "cli"
: "web";
return {
mode,
forwardedArgv: [...executable, ...userArgs.slice(index)],
hasPrefixModeFlag: prefixFlags.length > 0,
};
}