Skip to main content
Glama
cli.js10.3 kB
#!/usr/bin/env node import { Command } from "commander"; import fs from "node:fs"; import path from "node:path"; import { dirname, resolve } from "path"; 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); } function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms, true)); } async function runWebClient(args) { // Path to the client entry point const inspectorClientPath = resolve(__dirname, "../../", "client", "bin", "start.js"); const abort = new AbortController(); let cancelled = false; process.on("SIGINT", () => { cancelled = true; abort.abort(); }); // Build arguments to pass to start.js const startArgs = []; // Pass environment variables for (const [key, value] of Object.entries(args.envArgs)) { startArgs.push("-e", `${key}=${value}`); } // Pass transport type if specified if (args.transport) { startArgs.push("--transport", args.transport); } // Pass server URL if specified if (args.serverUrl) { startArgs.push("--server-url", args.serverUrl); } // Pass command and args (using -- to separate them) if (args.command) { startArgs.push("--", args.command, ...args.args); } try { await spawnPromise("node", [inspectorClientPath, ...startArgs], { signal: abort.signal, echoOutput: true, // pipe the stdout through here, prevents issues with buffering and // dropping the end of console.out after 8192 chars due to node // closing the stdout pipe before the output has finished flushing stdio: "inherit", }); } catch (e) { if (!cancelled || process.env.DEBUG) throw e; } } async function runCli(args) { const projectRoot = resolve(__dirname, ".."); const cliPath = resolve(projectRoot, "build", "index.js"); const abort = new AbortController(); let cancelled = false; process.on("SIGINT", () => { cancelled = true; abort.abort(); }); try { // Build CLI arguments const cliArgs = [cliPath]; // Add target URL/command first cliArgs.push(args.command, ...args.args); // Add transport flag if specified if (args.transport && args.transport !== "stdio") { // Convert streamable-http back to http for CLI mode const cliTransport = args.transport === "streamable-http" ? "http" : args.transport; cliArgs.push("--transport", cliTransport); } // Add headers if specified if (args.headers) { for (const [key, value] of Object.entries(args.headers)) { cliArgs.push("--header", `${key}: ${value}`); } } await spawnPromise("node", cliArgs, { env: { ...process.env, ...args.envArgs }, signal: abort.signal, echoOutput: true, // pipe the stdout through here, prevents issues with buffering and // dropping the end of console.out after 8192 chars due to node // closing the stdout pipe before the output has finished flushing stdio: "inherit", }); } 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; } 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 }; } function parseHeaderPair(value, previous = {}) { const colonIndex = value.indexOf(":"); if (colonIndex === -1) { throw new Error(`Invalid header format: ${value}. Use "HeaderName: Value" format.`); } const key = value.slice(0, colonIndex).trim(); const val = value.slice(colonIndex + 1).trim(); if (key === "" || val === "") { throw new Error(`Invalid header format: ${value}. Use "HeaderName: 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 <env>", "environment variables in KEY=VALUE format", parseKeyValuePair, {}) .option("--config <path>", "config file path") .option("--server <n>", "server name from config file") .option("--cli", "enable CLI mode") .option("--transport <type>", "transport type (stdio, sse, http)") .option("--server-url <url>", "server URL for SSE/HTTP transport") .option("--header <headers...>", 'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)', parseHeaderPair, {}); // 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 config and server options if (!options.config && options.server) { throw new Error("--server requires --config to be specified"); } // If config is provided without server, try to auto-select if (options.config && !options.server) { const configContent = fs.readFileSync(path.isAbsolute(options.config) ? options.config : path.resolve(process.cwd(), options.config), "utf8"); const parsedConfig = JSON.parse(configContent); const servers = Object.keys(parsedConfig.mcpServers || {}); if (servers.length === 1) { // Use the only server if there's just one options.server = servers[0]; } else if (servers.length === 0) { throw new Error("No servers found in config file"); } else { // Multiple servers, require explicit selection throw new Error(`Multiple servers found in config file. Please specify one with --server.\nAvailable servers: ${servers.join(", ")}`); } } // 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); if (config.type === "stdio") { return { command: config.command, args: [...(config.args || []), ...finalArgs], envArgs: { ...(config.env || {}), ...(options.e || {}) }, cli: options.cli || false, transport: "stdio", headers: options.header, }; } else if (config.type === "sse" || config.type === "streamable-http") { return { command: config.url, args: finalArgs, envArgs: options.e || {}, cli: options.cli || false, transport: config.type, serverUrl: config.url, headers: options.header, }; } else { // Backwards compatibility: if no type field, assume stdio return { command: config.command || "", args: [...(config.args || []), ...finalArgs], envArgs: { ...(config.env || {}), ...(options.e || {}) }, cli: options.cli || false, transport: "stdio", headers: options.header, }; } } // Otherwise use command line arguments const command = finalArgs[0] || ""; const args = finalArgs.slice(1); // Map "http" shorthand to "streamable-http" let transport = options.transport; if (transport === "http") { transport = "streamable-http"; } return { command, args, envArgs: options.e || {}, cli: options.cli || false, transport: transport, serverUrl: options.serverUrl, headers: options.header, }; } async function main() { process.on("uncaughtException", (error) => { handleError(error); }); try { const args = parseArgs(); if (args.cli) { await runCli(args); } else { await runWebClient(args); } } catch (error) { handleError(error); } } main();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kkShrihari/miEAA3_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server