Skip to main content
Glama

Token Saver MCP

by jerry426
import-memories.ts9.83 kB
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' import type { ToolMetadata } from '../types' import * as fs from 'node:fs' import * as path from 'node:path' import { z } from 'zod' import { memoryDb, MemoryScope } from '../../db/memory-db' /** * Tool metadata for documentation generation */ export const metadata: ToolMetadata = { name: 'import_memories', title: 'Import Memories', category: 'memory' as const, description: 'Import memories from a JSON backup file with smart conflict resolution.', docs: { brief: 'Restore memories from JSON backup', parameters: { file: 'Path to JSON file to import', mode: 'Import mode: merge (update existing), replace (overwrite all), or skip (only new)', onConflict: 'Conflict resolution: newer, older, higher_importance, or skip', scope: 'Override scope for all imported memories (optional)', minImportance: 'Only import memories at or above this importance (optional)', keyPrefix: 'Prefix to add to all imported keys (optional)', dryRun: 'Preview changes without importing (default: false)', backup: 'Create backup before importing (default: true)', }, examples: [ { title: 'Import from backup with smart merge', code: `import_memories({ file: "~/Dropbox/token-saver-backups/memories-2025-01-24.json", mode: "merge", onConflict: "newer" })`, }, { title: 'Preview import without changes', code: `import_memories({ file: "~/exports/shared-context.json", dryRun: true })`, }, { title: 'Import with namespace prefix', code: `import_memories({ file: "~/colleague-memories.json", keyPrefix: "imported.", mode: "skip" // Only add new keys })`, }, ], workflow: { usedWith: ['export_memories', 'list_memories'], followedBy: ['Verify imported memories with list_memories'], description: 'Restore memory state from backups', }, tips: [ 'Always preview with dryRun: true first', 'Automatic backup created before import by default', 'Use keyPrefix to namespace imported memories', 'Conflict resolution compares timestamps and importance', ], meta: { isUtility: true, }, }, } /** * Compare memories for conflict resolution */ function shouldReplaceMemory( existing: any, incoming: any, strategy: string, ): boolean { switch (strategy) { case 'newer': return new Date(incoming.updated_at || incoming.created_at) > new Date(existing.updated_at || existing.created_at) case 'older': return new Date(incoming.updated_at || incoming.created_at) < new Date(existing.updated_at || existing.created_at) case 'higher_importance': return (incoming.importance || 3) > (existing.importance || 3) case 'skip': default: return false } } // Tool handler export async function handler(params: { file: string mode?: 'merge' | 'replace' | 'skip' onConflict?: 'newer' | 'older' | 'higher_importance' | 'skip' scope?: 'global' | 'project' | 'session' | 'shared' minImportance?: number keyPrefix?: string dryRun?: boolean backup?: boolean } = { file: '' }): Promise<any> { try { if (!params.file) { return { content: [{ type: 'text', text: '❌ Error: file parameter is required', }], } } // Expand ~ to home directory const filePath = params.file.replace(/^~/, process.env.HOME || '') // Check file exists if (!fs.existsSync(filePath)) { return { content: [{ type: 'text', text: `❌ Error: File not found: ${filePath}`, }], } } // Read and parse JSON file const fileContent = fs.readFileSync(filePath, 'utf-8') let importData: any try { importData = JSON.parse(fileContent) } catch (e) { return { content: [{ type: 'text', text: '❌ Error: Invalid JSON file format', }], } } // Validate structure if (!importData.memories || !Array.isArray(importData.memories)) { return { content: [{ type: 'text', text: '❌ Error: Invalid export file structure (missing memories array)', }], } } const projectPath = process.cwd() const mode = params.mode || 'merge' const onConflict = params.onConflict || 'newer' const isDryRun = params.dryRun === true const shouldBackup = params.backup !== false // Create backup if not dry run and backup requested if (!isDryRun && shouldBackup) { // Use export tool to create backup const exportModule = await import('./export-memories') await exportModule.handler({ output: 'auto' }) } // Convert scope if provided const overrideScope = params.scope ? MemoryScope[params.scope.toUpperCase() as keyof typeof MemoryScope] : undefined // Track import statistics const stats = { total: importData.memories.length, imported: 0, updated: 0, skipped: 0, errors: 0, } const changes: string[] = [] // Process each memory for (const memory of importData.memories) { // Filter by importance if specified if (params.minImportance && (memory.importance || 3) < params.minImportance) { stats.skipped++ continue } // Apply key prefix if specified const key = params.keyPrefix ? `${params.keyPrefix}${memory.key}` : memory.key // Determine scope const scope = overrideScope || (memory.scope ? MemoryScope[memory.scope.toUpperCase() as keyof typeof MemoryScope] : MemoryScope.PROJECT) // Check if memory exists const existing = memoryDb.read({ key, scope, project_path: scope === MemoryScope.PROJECT ? projectPath : undefined, }) let shouldImport = false let action = '' if (!existing) { // New memory if (mode !== 'replace' || mode === 'replace') { shouldImport = true action = '➕ NEW' stats.imported++ } else { stats.skipped++ } } else if (mode === 'skip') { // Skip existing stats.skipped++ } else if (mode === 'replace') { // Always replace shouldImport = true action = '🔄 REPLACE' stats.updated++ } else if (mode === 'merge') { // Check conflict resolution if (shouldReplaceMemory(existing, memory, onConflict)) { shouldImport = true action = `🔄 UPDATE (${onConflict})` stats.updated++ } else { stats.skipped++ } } if (shouldImport) { changes.push(`${action}: ${key}`) if (!isDryRun) { try { memoryDb.write({ key, value: memory.value, scope, project_path: scope === MemoryScope.PROJECT ? projectPath : undefined, importance: memory.importance, verbosity: memory.verbosity, ttl: memory.ttl, tags: memory.tags, created_by: memory.created_by || 'import', }) } catch (e: any) { stats.errors++ changes.push(`❌ ERROR: ${key} - ${e.message}`) } } } } // Build result message let result = isDryRun ? '🔍 **Import Preview (Dry Run)**\n\n' : '✅ **Memories Imported Successfully**\n\n' result += `📊 **Statistics**:\n` result += `• Total in file: ${stats.total}\n` result += `• New imports: ${stats.imported}\n` result += `• Updates: ${stats.updated}\n` result += `• Skipped: ${stats.skipped}\n` if (stats.errors > 0) { result += `• Errors: ${stats.errors}\n` } if (importData.source) { result += `\n📁 **Source**:\n` result += `• File: ${path.basename(filePath)}\n` result += `• Exported: ${importData.exported_at}\n` result += `• From: ${importData.source.project_path}\n` } if (changes.length > 0 && changes.length <= 20) { result += `\n📝 **Changes**:\n` changes.forEach((change) => { result += `${change}\n` }) } else if (changes.length > 20) { result += `\n📝 **Changes**: ${changes.length} items (too many to list)\n` } if (isDryRun) { result += `\n💡 Remove dryRun: true to actually import these memories` } return { content: [{ type: 'text', text: result, }], } } catch (error: any) { return { content: [{ type: 'text', text: `❌ Error importing memories: ${error.message}`, }], } } } export function register(server: McpServer) { server.registerTool( metadata.name, { title: metadata.title, description: metadata.description, inputSchema: { file: z.string().describe('Path to JSON file to import'), mode: z.enum(['merge', 'replace', 'skip']).optional().describe('Import mode'), onConflict: z.enum(['newer', 'older', 'higher_importance', 'skip']).optional().describe('Conflict resolution'), scope: z.enum(['global', 'project', 'session', 'shared']).optional().describe('Override scope'), minImportance: z.number().min(1).max(5).optional().describe('Minimum importance filter'), keyPrefix: z.string().optional().describe('Prefix for imported keys'), dryRun: z.boolean().optional().describe('Preview without importing'), backup: z.boolean().optional().describe('Create backup before import'), }, }, handler, ) }

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/jerry426/token-saver-mcp'

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