import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import type { EmbeddingConfig } from '../embedding/index.js';
export interface MCPMemConfig {
embedding: EmbeddingConfig;
database?: {
path?: string;
};
}
export class ConfigManager {
private static CONFIG_FILE_NAME = 'mcpmem.config.json';
static loadConfig(): MCPMemConfig {
const configPath = join(process.cwd(), ConfigManager.CONFIG_FILE_NAME);
if (existsSync(configPath)) {
try {
const configFile = readFileSync(configPath, 'utf-8');
const config = JSON.parse(configFile) as MCPMemConfig;
return ConfigManager.validateAndNormalizeConfig(config);
} catch (error) {
console.warn(`Warning: Could not parse config file at ${configPath}:`, error);
}
}
return ConfigManager.loadFromEnvironment();
}
private static loadFromEnvironment(): MCPMemConfig {
const openaiApiKey = process.env.OPENAI_API_KEY;
if (!openaiApiKey) {
throw new Error(
'OPENAI_API_KEY environment variable is required. ' +
'Alternatively, create a mcpmem.config.json file with embedding configuration.',
);
}
return {
embedding: {
provider: 'openai',
apiKey: openaiApiKey,
model: process.env.OPENAI_MODEL || 'text-embedding-3-small',
baseURL: process.env.OPENAI_BASE_URL,
},
database: {
path: process.env.MCPMEM_DB_PATH,
},
};
}
private static validateAndNormalizeConfig(config: MCPMemConfig): MCPMemConfig {
if (!config.embedding) {
throw new Error('Embedding configuration is required');
}
if (!config.embedding.provider) {
throw new Error('Embedding provider is required');
}
if (config.embedding.provider === 'openai' && !config.embedding.apiKey) {
throw new Error('OpenAI API key is required for OpenAI provider');
}
return config;
}
static getDefaultConfig(): MCPMemConfig {
return {
embedding: {
provider: 'openai',
apiKey: 'your-openai-api-key-here',
model: 'text-embedding-3-small',
},
database: {
path: './mcpmem.db',
},
};
}
static createConfigFile(
config: MCPMemConfig,
path?: string,
): { configPath: string; gitignoreUpdated: boolean } {
const configPath = path || join(process.cwd(), ConfigManager.CONFIG_FILE_NAME);
const configJson = JSON.stringify(config, null, 2);
writeFileSync(configPath, configJson, 'utf-8');
const gitignoreUpdated = ConfigManager.updateGitignore();
return { configPath, gitignoreUpdated };
}
static updateGitignore(): boolean {
const gitignorePath = join(process.cwd(), '.gitignore');
const configEntry = 'mcpmem.config.json';
const dbEntry = 'mcpmem.db';
let gitignoreContent = '';
let needsUpdate = false;
if (existsSync(gitignorePath)) {
gitignoreContent = readFileSync(gitignorePath, 'utf-8');
const hasConfigEntry = gitignoreContent.includes(configEntry);
const hasDbEntry = gitignoreContent.includes(dbEntry);
if (!hasConfigEntry || !hasDbEntry) {
needsUpdate = true;
const entriesToAdd = [];
if (!hasConfigEntry) entriesToAdd.push(configEntry);
if (!hasDbEntry) entriesToAdd.push(dbEntry);
const newEntries = entriesToAdd.join('\n');
const separator = gitignoreContent.endsWith('\n') ? '' : '\n';
appendFileSync(gitignorePath, `${separator}${newEntries}\n`);
}
} else {
needsUpdate = true;
const newGitignore = `# MCPMem configuration and database files
${configEntry}
${dbEntry}
`;
writeFileSync(gitignorePath, newGitignore, 'utf-8');
}
return needsUpdate;
}
}