Skip to main content
Glama
index.js10.2 kB
#!/usr/bin/env node import * as fs from "fs"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { Command } from "commander"; import { callTool, connect, disconnect, getPrompt, listPrompts, listResources, listResourceTemplates, listTools, readResource, setLoggingLevel, validLogLevels, } from "./client/index.js"; import { handleError } from "./error-handler.js"; import { createTransport } from "./transport.js"; import { awaitableLog } from "./utils/awaitable-log.js"; function createTransportOptions(target, transport, headers) { if (target.length === 0) { throw new Error("Target is required. Specify a URL or a command to execute."); } const [command, ...commandArgs] = target; if (!command) { throw new Error("Command is required."); } const isUrl = command.startsWith("http://") || command.startsWith("https://"); if (isUrl && commandArgs.length > 0) { throw new Error("Arguments cannot be passed to a URL-based MCP server."); } let transportType; if (transport) { if (!isUrl && transport !== "stdio") { throw new Error("Only stdio transport can be used with local commands."); } if (isUrl && transport === "stdio") { throw new Error("stdio transport cannot be used with URLs."); } transportType = transport; } else if (isUrl) { const url = new URL(command); if (url.pathname.endsWith("/mcp")) { transportType = "http"; } else if (url.pathname.endsWith("/sse")) { transportType = "sse"; } else { transportType = "sse"; } } else { transportType = "stdio"; } return { transportType, command: isUrl ? undefined : command, args: isUrl ? undefined : commandArgs, url: isUrl ? command : undefined, headers, }; } async function callMethod(args) { // Read package.json to get name and version for client identity const pathA = "../package.json"; // We're in package @modelcontextprotocol/inspector-cli const pathB = "../../package.json"; // We're in package @modelcontextprotocol/inspector let packageJson; let packageJsonData = await import(fs.existsSync(pathA) ? pathA : pathB, { with: { type: "json" }, }); packageJson = packageJsonData.default; const transportOptions = createTransportOptions(args.target, args.transport, args.headers); const transport = createTransport(transportOptions); const [, name = packageJson.name] = packageJson.name.split("/"); const version = packageJson.version; const clientIdentity = { name, version }; const client = new Client(clientIdentity); try { await connect(client, transport); let result; // Tools methods if (args.method === "tools/list") { result = await listTools(client, args.metadata); } else if (args.method === "tools/call") { if (!args.toolName) { throw new Error("Tool name is required for tools/call method. Use --tool-name to specify the tool name."); } result = await callTool(client, args.toolName, args.toolArg || {}, args.metadata, args.toolMeta); } // Resources methods else if (args.method === "resources/list") { result = await listResources(client, args.metadata); } else if (args.method === "resources/read") { if (!args.uri) { throw new Error("URI is required for resources/read method. Use --uri to specify the resource URI."); } result = await readResource(client, args.uri, args.metadata); } else if (args.method === "resources/templates/list") { result = await listResourceTemplates(client, args.metadata); } // Prompts methods else if (args.method === "prompts/list") { result = await listPrompts(client, args.metadata); } else if (args.method === "prompts/get") { if (!args.promptName) { throw new Error("Prompt name is required for prompts/get method. Use --prompt-name to specify the prompt name."); } result = await getPrompt(client, args.promptName, args.promptArgs || {}, args.metadata); } // Logging methods else if (args.method === "logging/setLevel") { if (!args.logLevel) { throw new Error("Log level is required for logging/setLevel method. Use --log-level to specify the log level."); } result = await setLoggingLevel(client, args.logLevel); } else { throw new Error(`Unsupported method: ${args.method}. Supported methods include: tools/list, tools/call, resources/list, resources/read, resources/templates/list, prompts/list, prompts/get, logging/setLevel`); } await awaitableLog(JSON.stringify(result, null, 2)); } finally { try { await disconnect(transport); } catch (disconnectError) { throw disconnectError; } } } 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.`); } // Try to parse as JSON first let parsedValue; try { parsedValue = JSON.parse(val); } catch { // If JSON parsing fails, keep as string parsedValue = val; } return { ...previous, [key]: parsedValue }; } 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(); // Find if there's a -- in the arguments and split them 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-cli") .allowUnknownOption() .argument("<target...>", "Command and arguments or URL of the MCP server") // // Method selection // .option("--method <method>", "Method to invoke") // // Tool-related options // .option("--tool-name <toolName>", "Tool name (for tools/call method)") .option("--tool-arg <pairs...>", "Tool argument as key=value pair", parseKeyValuePair, {}) // // Resource-related options // .option("--uri <uri>", "URI of the resource (for resources/read method)") // // Prompt-related options // .option("--prompt-name <promptName>", "Name of the prompt (for prompts/get method)") .option("--prompt-args <pairs...>", "Prompt arguments as key=value pairs", parseKeyValuePair, {}) // // Logging options // .option("--log-level <level>", "Logging level (for logging/setLevel method)", (value) => { if (!validLogLevels.includes(value)) { throw new Error(`Invalid log level: ${value}. Valid levels are: ${validLogLevels.join(", ")}`); } return value; }) // // Transport options // .option("--transport <type>", "Transport type (sse, http, or stdio). Auto-detected from URL: /mcp → http, /sse → sse, commands → stdio", (value) => { const validTransports = ["sse", "http", "stdio"]; if (!validTransports.includes(value)) { throw new Error(`Invalid transport type: ${value}. Valid types are: ${validTransports.join(", ")}`); } return value; }) // // HTTP headers // .option("--header <headers...>", 'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)', parseHeaderPair, {}) // // Metadata options // .option("--metadata <pairs...>", "General metadata as key=value pairs (applied to all methods)", parseKeyValuePair, {}) .option("--tool-metadata <pairs...>", "Tool-specific metadata as key=value pairs (for tools/call method only)", parseKeyValuePair, {}); // Parse only the arguments before -- program.parse(preArgs); const options = program.opts(); let remainingArgs = program.args; // Add back any arguments that came after -- const finalArgs = [...remainingArgs, ...postArgs]; if (!options.method) { throw new Error("Method is required. Use --method to specify the method to invoke."); } return { target: finalArgs, ...options, headers: options.header, // commander.js uses 'header' field, map to 'headers' metadata: options.metadata ? Object.fromEntries(Object.entries(options.metadata).map(([key, value]) => [ key, String(value), ])) : undefined, toolMeta: options.toolMetadata ? Object.fromEntries(Object.entries(options.toolMetadata).map(([key, value]) => [ key, String(value), ])) : undefined, }; } async function main() { process.on("uncaughtException", (error) => { handleError(error); }); try { const args = parseArgs(); await callMethod(args); // Explicitly exit to ensure process terminates in CI process.exit(0); } 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