manage_preferences
Load user preferences at conversation start with 'get', or update default style, aspect ratio, model, style notes, and manage favorite prompts using 'set', 'add_favorite', and 'remove_favorite'.
Instructions
Read or update user preferences: default style, aspect ratio, model, style notes, and favorite prompts. Call with action "get" at conversation start to load preferences.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action to perform: "get" reads all preferences, "set" updates defaults/styleNotes, "add_favorite" saves a prompt, "remove_favorite" removes by index | |
| style | No | set: preferred default style (e.g. "realistic", "anime", "illustration") | |
| aspectRatio | No | set: preferred default aspect ratio. Use "auto" (recommended) to let MeiGen infer per-prompt, or pin a value like "16:9", "1:1", "9:16". | |
| model | No | set: preferred default model name | |
| provider | No | set: preferred default provider | |
| styleNotes | No | set: free-text style notes (e.g. "cinematic lighting, shallow DOF, brand colors #1A1A2E") | |
| prompt | No | add_favorite: the prompt text to save | |
| index | No | remove_favorite: 0-based index of the favorite to remove |
Implementation Reference
- src/tools/manage-preferences.ts:42-117 (handler)The main handler function 'registerManagePreferences' which registers the 'manage_preferences' tool on the MCP server. It handles four actions: 'get' (reads all preferences), 'set' (updates defaults/styleNotes), 'add_favorite' (saves a prompt), and 'remove_favorite' (removes by index).
export function registerManagePreferences(server: McpServer) { server.tool( 'manage_preferences', 'Read or update user preferences: default style, aspect ratio, model, style notes, and favorite prompts. Call with action "get" at conversation start to load preferences.', managePreferencesSchema, { readOnlyHint: false }, async ({ action, style, aspectRatio, model, provider, styleNotes, prompt, index }) => { switch (action) { case 'get': { const prefs = loadPreferences() return { content: [{ type: 'text' as const, text: JSON.stringify(prefs, null, 2), }], } } case 'set': { const updates: Record<string, string | undefined> = {} if (style !== undefined) updates.style = style if (aspectRatio !== undefined) updates.aspectRatio = aspectRatio if (model !== undefined) updates.model = model if (provider !== undefined) updates.provider = provider const prefs = updateDefaults(updates, styleNotes) return { content: [{ type: 'text' as const, text: `Preferences updated.\n${JSON.stringify(prefs.defaults, null, 2)}` + (styleNotes !== undefined ? `\nStyle notes: ${prefs.styleNotes}` : ''), }], } } case 'add_favorite': { if (!prompt) { return { content: [{ type: 'text' as const, text: 'Missing "prompt" parameter for add_favorite action.' }], isError: true, } } const prefs = addFavorite({ prompt, model, aspectRatio }) return { content: [{ type: 'text' as const, text: `Saved to favorites (${prefs.favorites.length} total).`, }], } } case 'remove_favorite': { if (index === undefined || index === null) { return { content: [{ type: 'text' as const, text: 'Missing "index" parameter for remove_favorite action.' }], isError: true, } } const prefs = removeFavorite(index) return { content: [{ type: 'text' as const, text: `Favorite removed (${prefs.favorites.length} remaining).`, }], } } default: return { content: [{ type: 'text' as const, text: `Unknown action: ${action}` }], isError: true, } } } ) } - The Zod schema 'managePreferencesSchema' defining the input parameters: action (enum of get/set/add_favorite/remove_favorite), style, aspectRatio, model, provider, styleNotes, prompt, and index.
export const managePreferencesSchema = { action: z.enum(['get', 'set', 'add_favorite', 'remove_favorite']) .describe('Action to perform: "get" reads all preferences, "set" updates defaults/styleNotes, "add_favorite" saves a prompt, "remove_favorite" removes by index'), // For 'set' action style: z.string().optional() .describe('set: preferred default style (e.g. "realistic", "anime", "illustration")'), aspectRatio: z.string().optional() .describe('set: preferred default aspect ratio. Use "auto" (recommended) to let MeiGen infer per-prompt, or pin a value like "16:9", "1:1", "9:16".'), model: z.string().optional() .describe('set: preferred default model name'), provider: z.enum(['openai', 'meigen', 'comfyui']).optional() .describe('set: preferred default provider'), styleNotes: z.string().optional() .describe('set: free-text style notes (e.g. "cinematic lighting, shallow DOF, brand colors #1A1A2E")'), // For 'add_favorite' action prompt: z.string().optional() .describe('add_favorite: the prompt text to save'), // For 'remove_favorite' action index: z.number().optional() .describe('remove_favorite: 0-based index of the favorite to remove'), } - src/server.ts:269-270 (registration)Registration call in the main server file that registers the 'manage_preferences' tool via 'registerManagePreferences(server)'.
registerManagePreferences(server) - src/server.ts:16-17 (registration)Import statement for 'registerManagePreferences' from the tool module.
import { registerManagePreferences } from './tools/manage-preferences.js' - src/lib/preferences.ts:1-147 (helper)Helper library providing low-level persistence functions: loadPreferences, savePreferences, updateDefaults, addFavorite, removeFavorite, and addRecentGeneration. Stores data at ~/.config/meigen/preferences.json.
/** * User preferences persistence * Storage: ~/.config/meigen/preferences.json * * Completely independent from config.ts (which handles provider credentials). * All functions are safe — read errors return defaults, write errors are silently ignored. */ import { readFileSync, writeFileSync, mkdirSync } from 'fs' import { join } from 'path' import { homedir } from 'os' // ── Types ────────────────────────────────────────────────────── export interface UserPreferences { version: number defaults: { style?: string aspectRatio?: string model?: string provider?: string } styleNotes: string favorites: FavoriteEntry[] recentSuccessful: RecentEntry[] } export interface FavoriteEntry { prompt: string model?: string aspectRatio?: string savedAt: string } export interface RecentEntry { prompt: string provider: string model?: string aspectRatio?: string generatedAt: string } // ── Constants ────────────────────────────────────────────────── const MAX_FAVORITES = 20 const MAX_RECENT = 10 function prefsPath(): string { return join(homedir(), '.config', 'meigen', 'preferences.json') } function emptyPreferences(): UserPreferences { return { version: 1, defaults: {}, styleNotes: '', favorites: [], recentSuccessful: [], } } // ── Read / Write ─────────────────────────────────────────────── export function loadPreferences(): UserPreferences { try { const raw = readFileSync(prefsPath(), 'utf-8') const parsed = JSON.parse(raw) as Partial<UserPreferences> // Merge with defaults to ensure all fields exist return { ...emptyPreferences(), ...parsed, defaults: { ...emptyPreferences().defaults, ...parsed.defaults }, } } catch { return emptyPreferences() } } export function savePreferences(prefs: UserPreferences): void { const filePath = prefsPath() mkdirSync(join(homedir(), '.config', 'meigen'), { recursive: true }) writeFileSync(filePath, JSON.stringify(prefs, null, 2) + '\n') } // ── Mutations (used by the tool) ─────────────────────────────── export function updateDefaults( updates: Partial<UserPreferences['defaults']>, styleNotes?: string, ): UserPreferences { const prefs = loadPreferences() for (const [key, value] of Object.entries(updates)) { if (value !== undefined) { (prefs.defaults as Record<string, string | undefined>)[key] = value || undefined } } if (styleNotes !== undefined) { prefs.styleNotes = styleNotes } savePreferences(prefs) return prefs } export function addFavorite(entry: Omit<FavoriteEntry, 'savedAt'>): UserPreferences { const prefs = loadPreferences() prefs.favorites.unshift({ ...entry, savedAt: new Date().toISOString(), }) if (prefs.favorites.length > MAX_FAVORITES) { prefs.favorites = prefs.favorites.slice(0, MAX_FAVORITES) } savePreferences(prefs) return prefs } export function removeFavorite(index: number): UserPreferences { const prefs = loadPreferences() if (index >= 0 && index < prefs.favorites.length) { prefs.favorites.splice(index, 1) } savePreferences(prefs) return prefs } // ── Auto-save (fire-and-forget, called from generate-image.ts) ─ export function addRecentGeneration(entry: { prompt: string provider: string model?: string aspectRatio?: string }): void { try { const prefs = loadPreferences() prefs.recentSuccessful.unshift({ ...entry, generatedAt: new Date().toISOString(), }) if (prefs.recentSuccessful.length > MAX_RECENT) { prefs.recentSuccessful = prefs.recentSuccessful.slice(0, MAX_RECENT) } savePreferences(prefs) } catch { // Never fail — this is a background enhancement } }