import { readFileSync } from "fs";
import { Config, ConfigSchema } from "./types.js";
export interface ConfigOptions {
configFile?: string;
baseUrl?: string;
swaggerUrl?: string;
authType?: "token" | "login";
token?: string;
username?: string;
password?: string;
loginEndpoint?: string;
tokenField?: string;
timeout?: number;
retries?: number;
}
export class ConfigLoader {
static loadConfig(): Config {
// 1. Try to load from command line arguments
const args = process.argv.slice(2);
const cliConfig = this.parseCliArgs(args);
// 2. Try to load from environment variables
const envConfig = this.parseEnvVars();
// 3. Try to load from config file (if specified)
let fileConfig: Partial<Config> = {};
const configFile =
cliConfig.configFile ||
envConfig.configFile ||
process.env.MCP_REST_CONFIG_FILE;
if (configFile) {
try {
const fileContent = readFileSync(configFile, "utf-8");
fileConfig = JSON.parse(fileContent);
} catch (error) {
console.warn(
`Warning: Could not load config file ${configFile}:`,
error instanceof Error ? error.message : "Unknown error"
);
}
}
// 4. Merge configurations (CLI > ENV > FILE) - only merge defined values
const mergedConfig = { ...fileConfig };
// Merge environment variables (only defined values)
Object.entries(envConfig).forEach(([key, value]) => {
if (value !== undefined) {
(mergedConfig as any)[key] = value;
}
});
// Merge CLI arguments (only defined values)
Object.entries(cliConfig).forEach(([key, value]) => {
if (value !== undefined) {
(mergedConfig as any)[key] = value;
}
});
// 5. Build the final configuration
const config = this.buildConfig(mergedConfig);
// 6. Validate the configuration
try {
return ConfigSchema.parse(config);
} catch (error) {
throw new Error(
`Invalid configuration: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
}
}
private static parseCliArgs(args: string[]): Partial<ConfigOptions> {
const config: Partial<ConfigOptions> = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
const nextArg = args[i + 1];
switch (arg) {
case "--config":
case "-c":
config.configFile = nextArg;
i++;
break;
case "--base-url":
config.baseUrl = nextArg;
i++;
break;
case "--swagger-url":
config.swaggerUrl = nextArg;
i++;
break;
case "--auth-type":
config.authType = nextArg as "token" | "login";
i++;
break;
case "--token":
config.token = nextArg;
i++;
break;
case "--username":
config.username = nextArg;
i++;
break;
case "--password":
config.password = nextArg;
i++;
break;
case "--login-endpoint":
config.loginEndpoint = nextArg;
i++;
break;
case "--token-field":
config.tokenField = nextArg;
i++;
break;
case "--timeout":
config.timeout = parseInt(nextArg, 10);
i++;
break;
case "--retries":
config.retries = parseInt(nextArg, 10);
i++;
break;
}
}
return config;
}
private static parseEnvVars(): Partial<ConfigOptions> {
return {
configFile: process.env.MCP_REST_CONFIG_FILE,
baseUrl: process.env.MCP_REST_BASE_URL,
swaggerUrl: process.env.MCP_REST_SWAGGER_URL,
authType: process.env.MCP_REST_AUTH_TYPE as "token" | "login",
token: process.env.MCP_REST_TOKEN,
username: process.env.MCP_REST_USERNAME,
password: process.env.MCP_REST_PASSWORD,
loginEndpoint: process.env.MCP_REST_LOGIN_ENDPOINT,
tokenField: process.env.MCP_REST_TOKEN_FIELD,
timeout: process.env.MCP_REST_TIMEOUT
? parseInt(process.env.MCP_REST_TIMEOUT, 10)
: undefined,
retries: process.env.MCP_REST_RETRIES
? parseInt(process.env.MCP_REST_RETRIES, 10)
: undefined,
};
}
private static buildConfig(
options: Partial<ConfigOptions> & Partial<Config>
): Partial<Config> {
// Handle direct config object (from file)
if (
options.auth &&
typeof options.auth === "object" &&
"type" in options.auth
) {
return options as Config;
}
// Handle flattened options (from CLI/env)
if (!options.baseUrl) {
throw new Error(
"Base URL is required. Provide it via --base-url, MCP_REST_BASE_URL, or config file."
);
}
if (!options.authType) {
throw new Error(
"Auth type is required. Provide it via --auth-type, MCP_REST_AUTH_TYPE, or config file."
);
}
let auth;
if (options.authType === "token") {
if (!options.token) {
throw new Error(
"Token is required for token authentication. Provide it via --token, MCP_REST_TOKEN, or config file."
);
}
auth = {
type: "token" as const,
token: options.token,
};
} else if (options.authType === "login") {
if (!options.username || !options.password || !options.loginEndpoint) {
throw new Error(
"Username, password, and login endpoint are required for login authentication."
);
}
auth = {
type: "login" as const,
username: options.username,
password: options.password,
loginEndpoint: options.loginEndpoint,
tokenField: options.tokenField || "access_token",
};
} else {
throw new Error(`Invalid auth type: ${options.authType}`);
}
return {
baseUrl: options.baseUrl,
swaggerUrl: options.swaggerUrl,
auth,
timeout: options.timeout || 30000,
retries: options.retries || 3,
};
}
static printUsage(): void {
console.log(`
MCP REST Server Configuration Options:
Command Line Arguments:
-c, --config <file> Path to JSON configuration file
--base-url <url> Base URL for the REST API (required)
--swagger-url <url> URL to Swagger/OpenAPI documentation
--auth-type <type> Authentication type: 'token' or 'login' (required)
--token <token> API token (required for token auth)
--username <username> Username (required for login auth)
--password <password> Password (required for login auth)
--login-endpoint <path> Login endpoint path (required for login auth)
--token-field <field> Token field name in login response (default: access_token)
--timeout <ms> Request timeout in milliseconds (default: 30000)
--retries <count> Number of retries for failed requests (default: 3)
Environment Variables:
MCP_REST_CONFIG_FILE Path to JSON configuration file
MCP_REST_BASE_URL Base URL for the REST API
MCP_REST_SWAGGER_URL URL to Swagger/OpenAPI documentation
MCP_REST_AUTH_TYPE Authentication type: 'token' or 'login'
MCP_REST_TOKEN API token
MCP_REST_USERNAME Username
MCP_REST_PASSWORD Password
MCP_REST_LOGIN_ENDPOINT Login endpoint path
MCP_REST_TOKEN_FIELD Token field name in login response
MCP_REST_TIMEOUT Request timeout in milliseconds
MCP_REST_RETRIES Number of retries for failed requests
Examples:
# Token authentication via command line
node dist/index.js --base-url https://api.example.com --auth-type token --token your-token
# Login authentication via environment variables
MCP_REST_BASE_URL=https://api.example.com MCP_REST_AUTH_TYPE=login MCP_REST_USERNAME=user MCP_REST_PASSWORD=pass MCP_REST_LOGIN_ENDPOINT=/auth/login node dist/index.js
# Using a configuration file
node dist/index.js --config ./api-config.json
`);
}
}