import chalk from 'chalk';
import { Command } from 'commander';
import ora from 'ora';
import { MCPMemoryServer } from './mcp/index.js';
import { ConfigManager } from './utils/index.js';
import { version } from './version.js';
const program = new Command();
program
.name('mcpmem')
.description('MCP tool for storing and searching memories with semantic search')
.version(version);
program
.command('start')
.description('Start the MCP memory server')
.option('-c, --config <path>', 'Path to config file')
.action(async _options => {
const spinner = ora('Starting MCP Memory Server...').start();
try {
const config = ConfigManager.loadConfig();
const server = new MCPMemoryServer(config);
spinner.succeed(chalk.green('MCP Memory Server started successfully'));
console.log(chalk.blue('Server is running and ready to accept MCP connections...'));
await server.run();
} catch (error) {
spinner.fail(chalk.red('Failed to start server'));
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
});
program
.command('init')
.description('Initialize a new mcpmem configuration file')
.option('-f, --force', 'Overwrite existing config file')
.action(options => {
const configPath = 'mcpmem.config.json';
if (!options.force && require('node:fs').existsSync(configPath)) {
console.error(
chalk.red('Configuration file already exists. Use --force to overwrite.'),
);
process.exit(1);
}
const defaultConfig = ConfigManager.getDefaultConfig();
const { gitignoreUpdated } = ConfigManager.createConfigFile(defaultConfig);
console.log(chalk.green('✓ Created mcpmem.config.json'));
if (gitignoreUpdated) {
console.log(chalk.green('✓ Updated .gitignore to exclude config and database files'));
} else {
console.log(chalk.blue('ℹ .gitignore already excludes config and database files'));
}
console.log(
chalk.yellow(
'Please edit the configuration file and add your API keys before starting the server.',
),
);
});
program
.command('test')
.description('Test the configuration and connection')
.action(async () => {
const spinner = ora('Testing configuration...').start();
try {
const config = ConfigManager.loadConfig();
spinner.text = 'Testing embedding service connection...';
if (config.embedding) {
const { EmbeddingService } = await import('./embedding/index.js');
const embeddingService = new EmbeddingService(config.embedding);
await embeddingService.generateEmbedding('test');
console.log(chalk.blue('✓ Embedding service: Connected'));
console.log(chalk.blue(`✓ Model: ${embeddingService.getModelName()}`));
console.log(chalk.blue(`✓ Dimensions: ${embeddingService.getDimensions()}`));
} else {
console.log(chalk.yellow('⚠ Embedding service: Not configured (text search only)'));
}
spinner.succeed(chalk.green('Configuration test passed'));
console.log(chalk.blue('✓ Database: Ready'));
} catch (error) {
spinner.fail(chalk.red('Configuration test failed'));
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
});
program
.command('store <content>')
.description('Store a new memory')
.option('-m, --metadata <json>', 'JSON metadata to attach')
.action(async (content, options) => {
const spinner = ora('Storing memory...').start();
try {
const config = ConfigManager.loadConfig();
const { MemoryService } = await import('./mcp/memory-service.js');
const memoryService = new MemoryService(config);
const embeddingInfo = memoryService.getEmbeddingInfo();
if (embeddingInfo.dimensions === 0) {
spinner.fail(
chalk.red(
'Embedding service not configured. Set OPENAI_API_KEY to store memories.',
),
);
process.exit(1);
}
let metadata: Record<string, unknown> | undefined;
if (options.metadata) {
try {
metadata = JSON.parse(options.metadata);
} catch (_error) {
spinner.fail(chalk.red('Invalid JSON metadata'));
process.exit(1);
}
}
const memory = await memoryService.storeMemory({ content, metadata });
spinner.succeed(chalk.green(`Memory stored with ID: ${memory.id}`));
console.log(chalk.blue('Content:'), content);
if (metadata) {
console.log(chalk.blue('Metadata:'), JSON.stringify(metadata, null, 2));
}
} catch (error) {
spinner.fail(chalk.red('Failed to store memory'));
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
});
program
.command('search <query>')
.description('Search memories using semantic similarity')
.option('-l, --limit <number>', 'Maximum number of results', '10')
.option('-t, --threshold <number>', 'Similarity threshold (0-1)')
.action(async (query, options) => {
const spinner = ora('Searching memories...').start();
try {
const config = ConfigManager.loadConfig();
const { MemoryService } = await import('./mcp/memory-service.js');
const memoryService = new MemoryService(config);
const limit = parseInt(options.limit, 10);
const threshold = options.threshold !== undefined ? parseFloat(options.threshold) : 0.4;
const embeddingInfo = memoryService.getEmbeddingInfo();
if (embeddingInfo.dimensions === 0) {
spinner.fail(
chalk.red(
'Embedding service not configured. Set OPENAI_API_KEY to use semantic search.',
),
);
process.exit(1);
}
const results = await memoryService.searchMemories(query, {
limit,
threshold,
});
spinner.succeed(chalk.green(`Found ${results.length} semantic matches`));
if (results.length === 0) {
console.log(chalk.yellow('No memories found matching your query.'));
return;
}
console.log(chalk.blue(`\nResults for: "${query}"\n`));
results.forEach((result, index) => {
const memory = 'memory' in result ? result.memory : result;
const similarity = 'similarity' in result ? result.similarity : undefined;
console.log(chalk.green(`${index + 1}. ${memory.id}`));
console.log(` Content: ${memory.content}`);
if (similarity) {
console.log(` Similarity: ${(similarity * 100).toFixed(1)}%`);
}
if (memory.metadata) {
console.log(` Metadata: ${JSON.stringify(memory.metadata)}`);
}
console.log(` Created: ${new Date(memory.created_at).toLocaleString()}`);
console.log('');
});
} catch (error) {
spinner.fail(chalk.red('Failed to search memories'));
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
});
program
.command('list')
.description('List all memories (most recent first)')
.option('-l, --limit <number>', 'Maximum number of memories to show', '20')
.action(async options => {
const spinner = ora('Loading memories...').start();
try {
const config = ConfigManager.loadConfig();
const { MemoryService } = await import('./mcp/memory-service.js');
const memoryService = new MemoryService(config);
const limit = parseInt(options.limit, 10);
const memories = await memoryService.getAllMemories(limit);
spinner.succeed(chalk.green(`Found ${memories.length} memories`));
if (memories.length === 0) {
console.log(chalk.yellow('No memories found.'));
return;
}
console.log(chalk.blue(`\nAll memories (showing ${memories.length}):\n`));
memories.forEach((memory, index) => {
console.log(chalk.green(`${index + 1}. ${memory.id}`));
console.log(` Content: ${memory.content}`);
if (memory.metadata) {
console.log(` Metadata: ${JSON.stringify(memory.metadata)}`);
}
console.log(` Created: ${new Date(memory.created_at).toLocaleString()}`);
if (memory.updated_at && memory.updated_at !== memory.created_at) {
console.log(` Updated: ${new Date(memory.updated_at).toLocaleString()}`);
}
console.log('');
});
} catch (error) {
spinner.fail(chalk.red('Failed to load memories'));
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
});
program
.command('get <id>')
.description('Get a specific memory by ID')
.action(async id => {
const spinner = ora(`Loading memory ${id}...`).start();
try {
const config = ConfigManager.loadConfig();
const { MemoryService } = await import('./mcp/memory-service.js');
const memoryService = new MemoryService(config);
const memory = await memoryService.getMemory(id);
if (!memory) {
spinner.fail(chalk.red(`Memory with ID ${id} not found`));
process.exit(1);
}
spinner.succeed(chalk.green(`Memory ${id} found`));
console.log(chalk.blue('\nMemory Details:\n'));
console.log(chalk.green(`ID: ${memory.id}`));
console.log(`Content: ${memory.content}`);
if (memory.metadata) {
console.log(`Metadata: ${JSON.stringify(memory.metadata, null, 2)}`);
}
console.log(`Created: ${new Date(memory.created_at).toLocaleString()}`);
if (memory.updated_at && memory.updated_at !== memory.created_at) {
console.log(`Updated: ${new Date(memory.updated_at).toLocaleString()}`);
}
} catch (error) {
spinner.fail(chalk.red('Failed to load memory'));
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
});
program
.command('delete <id>')
.description('Delete a memory by ID')
.option('-f, --force', 'Skip confirmation prompt')
.action(async (id, options) => {
try {
const config = ConfigManager.loadConfig();
const { MemoryService } = await import('./mcp/memory-service.js');
const memoryService = new MemoryService(config);
const memory = await memoryService.getMemory(id);
if (!memory) {
console.error(chalk.red(`Memory with ID ${id} not found`));
process.exit(1);
}
console.log(chalk.blue('Memory to delete:'));
console.log(`ID: ${memory.id}`);
console.log(`Content: ${memory.content}`);
console.log(`Created: ${new Date(memory.created_at).toLocaleString()}`);
if (!options.force) {
const readline = require('node:readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const answer = await new Promise<string>(resolve => {
rl.question(
chalk.yellow('\nAre you sure you want to delete this memory? (y/N): '),
resolve,
);
});
rl.close();
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
console.log(chalk.blue('Deletion cancelled.'));
return;
}
}
const spinner = ora(`Deleting memory ${id}...`).start();
const deleted = await memoryService.deleteMemory(id);
if (deleted) {
spinner.succeed(chalk.green(`Memory ${id} deleted successfully`));
} else {
spinner.fail(chalk.red(`Failed to delete memory ${id}`));
process.exit(1);
}
} catch (error) {
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
});
program
.command('clear')
.description('Delete all memories from the database')
.option('-f, --force', 'Skip confirmation prompt')
.action(async options => {
try {
const config = ConfigManager.loadConfig();
const { MemoryService } = await import('./mcp/memory-service.js');
const memoryService = new MemoryService(config);
// Get current count for confirmation
const currentCount = await memoryService.getMemoryCount();
if (currentCount === 0) {
console.log(chalk.yellow('No memories found in the database.'));
return;
}
console.log(chalk.blue(`Found ${currentCount} memories in the database.`));
if (!options.force) {
const readline = require('node:readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const answer = await new Promise<string>(resolve => {
rl.question(
chalk.yellow(
`\nAre you sure you want to delete ALL ${currentCount} memories? This cannot be undone. (y/N): `,
),
resolve,
);
});
rl.close();
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
console.log(chalk.blue('Operation cancelled.'));
return;
}
}
const spinner = ora('Deleting all memories...').start();
const deletedCount = await memoryService.clearAllMemories();
spinner.succeed(
chalk.green(`Successfully deleted ${deletedCount} memories from the database`),
);
} catch (error) {
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
});
program
.command('ls_db')
.description('Show database file location and details')
.action(async () => {
try {
const config = ConfigManager.loadConfig();
const dbPath = config.database?.path || './mcpmem.db';
const absolutePath = require('node:path').resolve(dbPath);
console.log(chalk.blue.bold('\n📁 Database File Information\n'));
console.log(chalk.green('Database path (config):'), dbPath);
console.log(chalk.green('Absolute path:'), absolutePath);
// Check if file exists
const fs = require('node:fs');
if (fs.existsSync(absolutePath)) {
const stats = fs.statSync(absolutePath);
console.log(chalk.green('File exists:'), '✅');
console.log(chalk.green('File size:'), `${(stats.size / 1024).toFixed(2)} KB`);
console.log(chalk.green('Last modified:'), stats.mtime.toLocaleString());
console.log(chalk.green('Created:'), stats.birthtime.toLocaleString());
// Try to get memory count
try {
const { MemoryService } = await import('./mcp/memory-service.js');
const memoryService = new MemoryService(config);
const count = await memoryService.getMemoryCount();
console.log(chalk.green('Total memories:'), count);
} catch (error) {
console.log(
chalk.yellow('Could not read memory count:'),
error instanceof Error ? error.message : 'Unknown error',
);
}
} else {
console.log(chalk.red('File exists:'), '❌');
console.log(chalk.yellow('Note:'), 'Database will be created on first use');
}
console.log(chalk.green('Working directory:'), process.cwd());
} catch (error) {
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
});
program
.command('stats')
.description('Show memory database statistics')
.action(async () => {
const spinner = ora('Loading statistics...').start();
try {
const config = ConfigManager.loadConfig();
const { MemoryService } = await import('./mcp/memory-service.js');
const memoryService = new MemoryService(config);
const count = await memoryService.getMemoryCount();
const embeddingInfo = memoryService.getEmbeddingInfo();
spinner.succeed(chalk.green('Statistics loaded'));
console.log(chalk.blue('\nMemory Database Statistics:\n'));
console.log(chalk.green(`Total memories: ${count}`));
console.log(`Embedding model: ${embeddingInfo.model}`);
console.log(`Embedding dimensions: ${embeddingInfo.dimensions}`);
console.log(`Database path: ${config.database?.path || './mcpmem.db'}`);
} catch (error) {
spinner.fail(chalk.red('Failed to load statistics'));
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
});
program
.command('help-commands')
.description('Show detailed help and examples for memory commands')
.action(() => {
console.log(chalk.blue.bold('\n🧠 MCPMem - Memory Management Commands\n'));
console.log(chalk.green.bold('📝 STORING MEMORIES'));
console.log(`${chalk.blue('mcpmem store')} <content>`);
console.log(' Store a simple memory:');
console.log(chalk.gray(' mcpmem store "Remember to review the quarterly reports"'));
console.log('');
console.log(' Store memory with metadata:');
console.log(
chalk.gray(
' mcpmem store "API endpoint returns 500 errors" -m \'{"project":"web-app","severity":"high"}\'',
),
);
console.log('');
console.log(chalk.green.bold('🔍 SEARCHING MEMORIES'));
console.log(`${chalk.blue('mcpmem search')} <query> [options]`);
console.log(' Semantic search (default):');
console.log(chalk.gray(' mcpmem search "database issues"'));
console.log('');
console.log(' Text search (faster, exact matches):');
console.log(chalk.gray(' mcpmem search "API" --text'));
console.log('');
console.log(' Custom limits and thresholds:');
console.log(chalk.gray(' mcpmem search "bugs" --limit 5 --threshold 0.8'));
console.log('');
console.log(chalk.green.bold('📋 LISTING MEMORIES'));
console.log(`${chalk.blue('mcpmem list')} [options]`);
console.log(' Show recent memories:');
console.log(chalk.gray(' mcpmem list'));
console.log('');
console.log(' Show more memories:');
console.log(chalk.gray(' mcpmem list --limit 50'));
console.log('');
console.log(chalk.green.bold('🔍 GETTING SPECIFIC MEMORY'));
console.log(`${chalk.blue('mcpmem get')} <id>`);
console.log(' Get memory details:');
console.log(chalk.gray(' mcpmem get abc123-def456-789'));
console.log('');
console.log(chalk.green.bold('🗑️ DELETING MEMORIES'));
console.log(`${chalk.blue('mcpmem delete')} <id> [options]`);
console.log(' Delete with confirmation:');
console.log(chalk.gray(' mcpmem delete abc123-def456-789'));
console.log('');
console.log(' Force delete (no confirmation):');
console.log(chalk.gray(' mcpmem delete abc123-def456-789 --force'));
console.log('');
console.log(chalk.green.bold('📊 DATABASE INFO'));
console.log(chalk.blue('mcpmem ls_db'));
console.log(' Show database file location and details:');
console.log(chalk.gray(' mcpmem ls_db'));
console.log('');
console.log(chalk.blue('mcpmem stats'));
console.log(' Show database statistics:');
console.log(chalk.gray(' mcpmem stats'));
console.log('');
console.log(chalk.green.bold('⚙️ CONFIGURATION'));
console.log(`${chalk.blue('mcpmem init')} - Initialize configuration file`);
console.log(`${chalk.blue('mcpmem test')} - Test configuration and connection`);
console.log(`${chalk.blue('mcpmem start')} - Start MCP server (or just run 'mcpmem')`);
console.log('');
console.log(`${chalk.yellow.bold('💡 TIP:')} Use environment variables for configuration:`);
console.log(chalk.gray(' export OPENAI_API_KEY=sk-...'));
console.log(chalk.gray(' export OPENAI_MODEL=text-embedding-3-small'));
console.log(chalk.gray(' export MCPMEM_DB_PATH=/path/to/memories.db'));
console.log('');
console.log(chalk.blue.bold('📚 For more help: mcpmem <command> --help'));
});
if (process.argv.length === 2) {
(async () => {
try {
const config = ConfigManager.loadConfig();
const server = new MCPMemoryServer(config);
await server.run();
} catch (error) {
console.error(
chalk.red('Error:'),
error instanceof Error ? error.message : 'Unknown error',
);
process.exit(1);
}
})();
} else {
program.parse();
}