diff --git a/bin/cli.js b/bin/cli.js index 8938dda..6b66f75 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -7,186 +7,212 @@ import { spawnPromise } from "spawn-rx"; import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); function handleError(error) { - let message; - if (error instanceof Error) { - message = error.message; - } - else if (typeof error === "string") { - message = error; - } - else { - message = "Unknown error"; - } - console.error(message); - process.exit(1); + let message; + if (error instanceof Error) { + message = error.message; + } else if (typeof error === "string") { + message = error; + } else { + message = "Unknown error"; + } + console.error(message); + process.exit(1); } function delay(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } async function runWebClient(args) { - const inspectorServerPath = resolve(__dirname, "..", "server", "build", "index.js"); - // Path to the client entry point - const inspectorClientPath = resolve(__dirname, "..", "client", "bin", "cli.js"); - const CLIENT_PORT = process.env.CLIENT_PORT ?? "5173"; - const SERVER_PORT = process.env.SERVER_PORT ?? "3000"; - console.log("Starting MCP inspector..."); - const abort = new AbortController(); - let cancelled = false; - process.on("SIGINT", () => { - cancelled = true; - abort.abort(); - }); - const server = spawnPromise("node", [ - inspectorServerPath, - ...(args.command ? [`--env`, args.command] : []), - ...(args.args ? [`--args=${args.args.join(" ")}`] : []), - ], { - env: { - ...process.env, - PORT: SERVER_PORT, - MCP_ENV_VARS: JSON.stringify(args.envArgs), - }, - signal: abort.signal, - echoOutput: true, - }); - const client = spawnPromise("node", [inspectorClientPath], { - env: { ...process.env, PORT: CLIENT_PORT }, - signal: abort.signal, - echoOutput: true, - }); - // Make sure our server/client didn't immediately fail - await Promise.any([server, client, delay(2 * 1000)]); - const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`; - console.log(`\nšŸ” MCP Inspector is up and running at http://127.0.0.1:${CLIENT_PORT}${portParam} šŸš€`); - try { - await Promise.any([server, client]); - } - catch (e) { - if (!cancelled || process.env.DEBUG) { - throw e; - } + const inspectorServerPath = resolve( + __dirname, + "..", + "server", + "build", + "index.js", + ); + // Path to the client entry point + const inspectorClientPath = resolve( + __dirname, + "..", + "client", + "bin", + "cli.js", + ); + const CLIENT_PORT = process.env.CLIENT_PORT ?? "5173"; + const SERVER_PORT = process.env.SERVER_PORT ?? "3000"; + console.log("Starting MCP inspector..."); + const abort = new AbortController(); + let cancelled = false; + process.on("SIGINT", () => { + cancelled = true; + abort.abort(); + }); + const server = spawnPromise( + "node", + [ + inspectorServerPath, + ...(args.command ? [`--env`, args.command] : []), + ...(args.args ? [`--args=${args.args.join(" ")}`] : []), + ], + { + env: { + ...process.env, + PORT: SERVER_PORT, + MCP_ENV_VARS: JSON.stringify(args.envArgs), + }, + signal: abort.signal, + echoOutput: true, + }, + ); + const client = spawnPromise("node", [inspectorClientPath], { + env: { ...process.env, PORT: CLIENT_PORT }, + signal: abort.signal, + echoOutput: true, + }); + // Make sure our server/client didn't immediately fail + await Promise.any([server, client, delay(2 * 1000)]); + const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`; + console.log( + `\nšŸ” MCP Inspector is up and running at http://127.0.0.1:${CLIENT_PORT}${portParam} šŸš€`, + ); + try { + await Promise.any([server, client]); + } catch (e) { + if (!cancelled || process.env.DEBUG) { + throw e; } + } } async function runCli(args) { - const projectRoot = resolve(__dirname, ".."); - const cliPath = resolve(projectRoot, "cli", "build", "index.js"); - const abort = new AbortController(); - let cancelled = false; - process.on("SIGINT", () => { - cancelled = true; - abort.abort(); + const projectRoot = resolve(__dirname, ".."); + const cliPath = resolve(projectRoot, "cli", "build", "index.js"); + const abort = new AbortController(); + let cancelled = false; + process.on("SIGINT", () => { + cancelled = true; + abort.abort(); + }); + try { + await spawnPromise("node", [cliPath, args.command, ...args.args], { + env: { ...process.env, ...args.envArgs }, + signal: abort.signal, + echoOutput: true, }); - try { - await spawnPromise("node", [cliPath, args.command, ...args.args], { - env: { ...process.env, ...args.envArgs }, - signal: abort.signal, - echoOutput: true, - }); - } - catch (e) { - if (!cancelled || process.env.DEBUG) { - throw e; - } + } catch (e) { + if (!cancelled || process.env.DEBUG) { + throw e; } + } } function loadConfigFile(configPath, serverName) { - try { - const resolvedConfigPath = path.isAbsolute(configPath) - ? configPath - : path.resolve(process.cwd(), configPath); - if (!fs.existsSync(resolvedConfigPath)) { - throw new Error(`Config file not found: ${resolvedConfigPath}`); - } - const configContent = fs.readFileSync(resolvedConfigPath, "utf8"); - const parsedConfig = JSON.parse(configContent); - if (!parsedConfig.mcpServers || !parsedConfig.mcpServers[serverName]) { - const availableServers = Object.keys(parsedConfig.mcpServers || {}).join(", "); - throw new Error(`Server '${serverName}' not found in config file. Available servers: ${availableServers}`); - } - const serverConfig = parsedConfig.mcpServers[serverName]; - return serverConfig; + try { + const resolvedConfigPath = path.isAbsolute(configPath) + ? configPath + : path.resolve(process.cwd(), configPath); + if (!fs.existsSync(resolvedConfigPath)) { + throw new Error(`Config file not found: ${resolvedConfigPath}`); } - catch (err) { - if (err instanceof SyntaxError) { - throw new Error(`Invalid JSON in config file: ${err.message}`); - } - throw err; + const configContent = fs.readFileSync(resolvedConfigPath, "utf8"); + const parsedConfig = JSON.parse(configContent); + if (!parsedConfig.mcpServers || !parsedConfig.mcpServers[serverName]) { + const availableServers = Object.keys(parsedConfig.mcpServers || {}).join( + ", ", + ); + throw new Error( + `Server '${serverName}' not found in config file. Available servers: ${availableServers}`, + ); } + const serverConfig = parsedConfig.mcpServers[serverName]; + return serverConfig; + } catch (err) { + if (err instanceof SyntaxError) { + throw new Error(`Invalid JSON in config file: ${err.message}`); + } + throw err; + } } function parseKeyValuePair(value, previous = {}) { - const parts = value.split("="); - const key = parts[0]; - const val = parts.slice(1).join("="); - if (val === undefined || val === "") { - throw new Error(`Invalid parameter format: ${value}. Use key=value format.`); - } - return { ...previous, [key]: val }; + const parts = value.split("="); + const key = parts[0]; + const val = parts.slice(1).join("="); + if (val === undefined || val === "") { + throw new Error( + `Invalid parameter format: ${value}. Use key=value format.`, + ); + } + return { ...previous, [key]: val }; } function parseArgs() { - const program = new Command(); - const argSeparatorIndex = process.argv.indexOf("--"); - let preArgs = process.argv; - let postArgs = []; - if (argSeparatorIndex !== -1) { - preArgs = process.argv.slice(0, argSeparatorIndex); - postArgs = process.argv.slice(argSeparatorIndex + 1); - } - program - .name("inspector-bin") - .allowExcessArguments() - .allowUnknownOption() - .option("-e ", "environment variables in KEY=VALUE format", parseKeyValuePair, {}) - .option("--config ", "config file path") - .option("--server ", "server name from config file") - .option("--cli", "enable CLI mode"); - // Parse only the arguments before -- - program.parse(preArgs); - const options = program.opts(); - const remainingArgs = program.args; - // Add back any arguments that came after -- - const finalArgs = [...remainingArgs, ...postArgs]; - // Validate that config and server are provided together - if ((options.config && !options.server) || - (!options.config && options.server)) { - throw new Error("Both --config and --server must be provided together. If you specify one, you must specify the other."); - } - // If config file is specified, load and use the options from the file. We must merge the args - // from the command line and the file together, or we will miss the method options (--method, - // etc.) - if (options.config && options.server) { - const config = loadConfigFile(options.config, options.server); - return { - command: config.command, - args: [...(config.args || []), ...finalArgs], - envArgs: { ...(config.env || {}), ...(options.e || {}) }, - cli: options.cli || false, - }; - } - // Otherwise use command line arguments - const command = finalArgs[0] || ""; - const args = finalArgs.slice(1); + const program = new Command(); + const argSeparatorIndex = process.argv.indexOf("--"); + let preArgs = process.argv; + let postArgs = []; + if (argSeparatorIndex !== -1) { + preArgs = process.argv.slice(0, argSeparatorIndex); + postArgs = process.argv.slice(argSeparatorIndex + 1); + } + program + .name("inspector-bin") + .allowExcessArguments() + .allowUnknownOption() + .option( + "-e ", + "environment variables in KEY=VALUE format", + parseKeyValuePair, + {}, + ) + .option("--config ", "config file path") + .option("--server ", "server name from config file") + .option("--cli", "enable CLI mode"); + // Parse only the arguments before -- + program.parse(preArgs); + const options = program.opts(); + const remainingArgs = program.args; + // Add back any arguments that came after -- + const finalArgs = [...remainingArgs, ...postArgs]; + // Validate that config and server are provided together + if ( + (options.config && !options.server) || + (!options.config && options.server) + ) { + throw new Error( + "Both --config and --server must be provided together. If you specify one, you must specify the other.", + ); + } + // If config file is specified, load and use the options from the file. We must merge the args + // from the command line and the file together, or we will miss the method options (--method, + // etc.) + if (options.config && options.server) { + const config = loadConfigFile(options.config, options.server); return { - command, - args, - envArgs: options.e || {}, - cli: options.cli || false, + command: config.command, + args: [...(config.args || []), ...finalArgs], + envArgs: { ...(config.env || {}), ...(options.e || {}) }, + cli: options.cli || false, }; + } + // Otherwise use command line arguments + const command = finalArgs[0] || ""; + const args = finalArgs.slice(1); + return { + command, + args, + envArgs: options.e || {}, + cli: options.cli || false, + }; } async function main() { - process.on("uncaughtException", (error) => { - handleError(error); - }); - try { - const args = parseArgs(); - if (args.cli) { - runCli(args); - } - else { - await runWebClient(args); - } - } - catch (error) { - handleError(error); + process.on("uncaughtException", (error) => { + handleError(error); + }); + try { + const args = parseArgs(); + if (args.cli) { + runCli(args); + } else { + await runWebClient(args); } + } catch (error) { + handleError(error); + } } main(); diff --git a/package.json b/package.json index 580cc7c..1882083 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dev": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev\"", "dev:windows": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev:windows\"", "test": "npm run prettier-check && cd client && npm test", - "test:cli": "cd bin && npm run test", + "test-cli": "cd bin && npm run test", "build-bin": "cd bin && npm run build", "build-server": "cd server && npm run build", "build-client": "cd client && npm run build",