#!/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();
