Skip to main content
Glama

mcp-confluent

Official
by confluentinc
cli.ts10.3 kB
import { Command, CommanderError, Option } from "@commander-js/extra-typings"; import { ToolName } from "@src/confluent/tools/tool-name.js"; import { logger } from "@src/logger.js"; import { TransportType } from "@src/mcp/transports/types.js"; import * as dotenv from "dotenv"; import fs from "fs"; import path from "path"; import { getProperties, KeyValuePairObject } from "properties-file"; import pkg from "../package.json" with { type: "json" }; // Define the interface for our CLI options export interface CLIOptions { envFile?: string; transports: TransportType[]; allowTools?: string[]; blockTools?: string[]; listTools?: boolean; disableConfluentCloudTools?: boolean; kafkaConfig: KeyValuePairObject; } /** * Get the package version from package.json * @returns Package version string */ export function getPackageVersion(): string { return pkg.version; } function parseTransportList(value: string): TransportType[] { // Split, trim, and filter out empty strings const types = value .split(",") .map((type) => type.trim()) .filter(Boolean); // Validate each transport type const validTypes = new Set(Object.values(TransportType)); const invalidTypes = types.filter( (type) => !validTypes.has(type as TransportType), ); if (invalidTypes.length > 0) { throw new Error( `Invalid transport type(s): ${invalidTypes.join(", ")}. Valid options: ${Array.from(validTypes).join(", ")}`, ); } // Deduplicate using Set return Array.from(new Set(types)) as TransportType[]; } /** * Parses a comma-separated list of tool names and returns an array of valid tool names. * Trims whitespace from each tool name. * Exits the process with an error if any invalid tool names are provided. * * @param value - Comma-separated list of tool names * @returns Array of valid tool names */ function parseToolList(value: string): string[] { return ( value .split(",") .map((t) => t.trim()) // Filter out any empty strings that may result from extra commas or whitespace .filter(Boolean) ); } /** * Reads a file and returns an array of non-empty, non-comment lines. * Lines starting with '#' are treated as comments and ignored. * Trims whitespace from each line. * Exits the process with an error if the file does not exist. * * @param filePath - Path to the file to read * @returns Array of valid lines from the file */ function readFileLines(filePath: string): string[] { const absPath = path.resolve(filePath); if (!fs.existsSync(absPath)) { throw new Error(`Tool list file not found: ${absPath}`); } const lines = fs .readFileSync(absPath, "utf-8") .split("\n") .map((line) => line.trim()) .filter((line) => line && !line.startsWith("#")); return lines; } /** * Parse command line arguments with strong typing * @returns Parsed CLI options */ export function parseCliArgs(): CLIOptions { const program = new Command() .name("mcp-confluent") .description( "Confluent MCP Server - Model Context Protocol implementation for Confluent Cloud", ) .version(getPackageVersion()) .option("-e, --env-file <path>", "Load environment variables from file") .option( "-k, --kafka-config-file <file>", "Path to a properties file for configuring kafka clients", ) .addOption( new Option( "-t, --transport <types>", "Transport types (comma-separated list)", ) .choices(Object.values(TransportType)) .default(TransportType.STDIO) .argParser((value) => parseTransportList(value)), ) .option( "--allow-tools <tools>", "Comma-separated list of tool names to allow. If provided, takes precedence over --allow-tools-file. Allow-list is applied before block-list.", ) .option( "--block-tools <tools>", "Comma-separated list of tool names to block. If provided, takes precedence over --block-tools-file. Block-list is applied after allow-list.", ) .option( "--allow-tools-file <file>", "File with tool names to allow (one per line). Used only if --allow-tools is not provided. Allow-list is applied before block-list.", ) .option( "--block-tools-file <file>", "File with tool names to block (one per line). Used only if --block-tools is not provided. Block-list is applied after allow-list.", ) .option( "--list-tools", "Print the final set of enabled tool names (with descriptions) after allow/block filtering and exit. Does not start the server.", ) .option( "--disable-confluent-cloud-tools", "Disable all tools that require Confluent Cloud REST APIs (cloud-only tools).", ) .allowExcessArguments(false) .exitOverride(); try { const opts = program.parse().opts(); if (opts.envFile) { loadEnvironmentVariables(opts.envFile); } // Precedence: CLI > file > undefined let allowTools: string[] | undefined = undefined; let blockTools: string[] | undefined = undefined; if (opts.allowTools) { allowTools = parseToolList(opts.allowTools); } else if (opts.allowToolsFile) { allowTools = readFileLines(opts.allowToolsFile); } if (opts.blockTools) { blockTools = parseToolList(opts.blockTools); } else if (opts.blockToolsFile) { blockTools = readFileLines(opts.blockToolsFile); } let kafkaConfig: KeyValuePairObject = {}; if (opts.kafkaConfigFile) { kafkaConfig = parsePropertiesFile(opts.kafkaConfigFile); } return { envFile: opts.envFile, transports: Array.isArray(opts.transport) ? opts.transport : [opts.transport], allowTools, blockTools, listTools: !!opts.listTools, disableConfluentCloudTools: !!opts.disableConfluentCloudTools, kafkaConfig: kafkaConfig, }; } catch (error: unknown) { if ( error instanceof CommanderError && (error.code === "commander.helpDisplayed" || error.code === "commander.version") ) { process.exit(0); } if (error instanceof Error) { logger.error( { error, errorString: error.toString(), errorMessage: error.message, }, "Error parsing CLI options", ); } else { logger.error({ error: String(error) }, "Error parsing CLI options"); } process.exit(1); } } /** * Load environment variables from file * @param envFile Path to the environment file */ export function loadEnvironmentVariables(envFile: string): void { const envPath = path.resolve(envFile); // Check if file exists if (!fs.existsSync(envPath)) { throw new Error(`Environment file not found: ${envPath}`); } // Load environment variables from file const result = dotenv.config({ path: envPath }); if (result.error) { throw new Error(`Error loading environment variables: ${result.error}`); } logger.info(`Loaded environment variables from ${envPath}`); } /** * Filters and returns a sorted list of enabled ToolNames based on CLI allow/block options. * * This function determines which tools should be enabled for the server by applying * the following logic: * 1. If an allow list (`cliOptions.allowTools`) is provided and non-empty, only those * tool names present in the allow list (and valid) will be enabled. Any invalid * tool names in the allow list are ignored and a warning is logged. * 2. If a block list (`cliOptions.blockTools`) is provided and non-empty, any tool * names present in the block list (and valid) will be removed from the enabled * set. Any invalid tool names in the block list are ignored and a warning is logged. * 3. If neither allow nor block lists are provided, all available tools are enabled. * * The returned list is always sorted alphabetically. * * @param cliOptions - The parsed CLI options containing allow/block tool lists. * @returns An alphabetically sorted array of enabled ToolNames. */ export function getFilteredToolNames(cliOptions: CLIOptions): ToolName[] { let filteredToolNames: ToolName[] = Object.values(ToolName); const validToolNames = new Set(Object.values(ToolName)); if (cliOptions.allowTools && cliOptions.allowTools.length > 0) { const valid = cliOptions.allowTools.filter((t) => validToolNames.has(t as ToolName), ); const invalid = cliOptions.allowTools.filter( (t) => !validToolNames.has(t as ToolName), ); if (invalid.length > 0) { logger.warn( `Ignoring invalid tool names in allow list: ${invalid.join(", ")}`, ); } filteredToolNames = valid.map((t) => t as ToolName); } if (cliOptions.blockTools && cliOptions.blockTools.length > 0) { const validBlock = cliOptions.blockTools.filter((t) => validToolNames.has(t as ToolName), ); const invalidBlock = cliOptions.blockTools.filter( (t) => !validToolNames.has(t as ToolName), ); if (invalidBlock.length > 0) { logger.warn( `Ignoring invalid tool names in block list: ${invalidBlock.join(", ")}`, ); } filteredToolNames = filteredToolNames.filter( (t) => !validBlock.includes(t), ); } // Deduplicate and sort const deduped = Array.from(new Set(filteredToolNames)).sort(); if ( (!cliOptions.allowTools || cliOptions.allowTools.length === 0) && (!cliOptions.blockTools || cliOptions.blockTools.length === 0) ) { logger.info( "No allow/block tool lists provided; all tools are enabled by default.", ); } return deduped; } /** * Loads configuration from a properties file * @param filePath - Path to the properties file * @returns configuration object */ export function parsePropertiesFile(filePath: string): KeyValuePairObject { const absPath = path.resolve(filePath); if (!fs.existsSync(absPath)) { throw new Error(`Properties file not found: ${absPath}`); } try { const properties: KeyValuePairObject = getProperties( fs.readFileSync(absPath, "utf-8"), ); return properties; } catch (err) { throw new Error( `Failed to parse properties file: ${err instanceof Error ? err.message : String(err)}`, ); } }

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/confluentinc/mcp-confluent'

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