/**
* Configuration management for the MCP server.
*
* Reads configuration from ~/.config/onboarded-mcp/config.json
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
export interface Config {
/** Path to the local onboarded repo for source code inspection */
onboardedRepoPath?: string;
/** Default profile to use if not specified */
defaultProfile?: string;
/** Base URLs for different environments */
baseUrls?: {
prod?: {
v1: string;
internal: string;
};
staging?: {
v1: string;
internal: string;
};
};
}
const CONFIG_DIR = join(homedir(), ".config", "onboarded-mcp");
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
const CACHE_DIR = join(homedir(), ".cache", "onboarded-mcp");
const DATA_DIR = join(homedir(), ".local", "share", "onboarded-mcp");
// Default OpenAPI spec URLs
export const DEFAULT_SPEC_URLS = {
prod: {
v1: "https://app.onboarded.com/api/v1/openapi.json",
internal: "https://app.onboarded.com/api/internal/openapi.json",
},
staging: {
v1: "https://staging.onboarded.com/api/v1/openapi.json",
internal: "https://staging.onboarded.com/api/internal/openapi.json",
},
} as const;
// Default API base URLs (for executing requests, not fetching specs)
export const DEFAULT_API_BASE_URLS = {
prod: {
v1: "https://app.onboarded.com",
internal: "https://app.onboarded.com",
},
staging: {
v1: "https://staging.onboarded.com",
internal: "https://staging.onboarded.com",
},
} as const;
let cachedConfig: Config | null = null;
/**
* Ensure required directories exist.
*/
export function ensureDirectories(): void {
for (const dir of [CONFIG_DIR, CACHE_DIR, DATA_DIR]) {
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
}
}
/**
* Load configuration from disk.
*/
export function loadConfig(): Config {
if (cachedConfig) {
return cachedConfig;
}
ensureDirectories();
if (!existsSync(CONFIG_FILE)) {
cachedConfig = {};
return cachedConfig;
}
try {
const content = readFileSync(CONFIG_FILE, "utf-8");
cachedConfig = JSON.parse(content) as Config;
return cachedConfig;
} catch (error) {
console.error(`Failed to load config from ${CONFIG_FILE}:`, error);
cachedConfig = {};
return cachedConfig;
}
}
/**
* Save configuration to disk.
*/
export function saveConfig(config: Config): void {
ensureDirectories();
try {
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
cachedConfig = config;
} catch (error) {
console.error(`Failed to save config to ${CONFIG_FILE}:`, error);
throw error;
}
}
/**
* Get the cache directory path.
*/
export function getCacheDir(): string {
ensureDirectories();
return CACHE_DIR;
}
/**
* Get the data directory path (for SQLite database).
*/
export function getDataDir(): string {
ensureDirectories();
return DATA_DIR;
}
/**
* Get the OpenAPI spec URL for the given API and environment.
*/
export function getSpecUrl(
api: "v1" | "internal",
env: "prod" | "staging" = "prod"
): string {
const config = loadConfig();
return config.baseUrls?.[env]?.[api] ?? DEFAULT_SPEC_URLS[env][api];
}
/**
* Get the API base URL for the given API and environment.
*/
export function getApiBaseUrl(
api: "v1" | "internal",
env: "prod" | "staging" = "prod"
): string {
return DEFAULT_API_BASE_URLS[env][api];
}
/**
* Get the configured onboarded repo path, if any.
*/
export function getOnboardedRepoPath(): string | undefined {
const config = loadConfig();
return config.onboardedRepoPath;
}
/**
* Get the default profile name.
*/
export function getDefaultProfile(): string | undefined {
const config = loadConfig();
return config.defaultProfile;
}
/**
* Clear the config cache (useful for testing).
*/
export function clearConfigCache(): void {
cachedConfig = null;
}
/**
* Alias for ensureDirectories (for clearer naming in server startup).
*/
export const ensureConfigDirs = ensureDirectories;