From c0e616212d5c79ce8a4ef8d2bdbf35e0ddbd99ba Mon Sep 17 00:00:00 2001 From: josedario87 Date: Tue, 17 Feb 2026 02:33:35 -0600 Subject: [PATCH] feat: Add read_component and edit_component MCP tools Surgical read/edit tools for saved Vue components, avoiding full rewrites via save_vue_component. edit_component supports replace_all for non-unique strings. Token-optimized schemas and responses. --- frontend/src/services/toolRegistry.ts | 2 +- .../tools/handlers/componentHandlers.ts | 101 ++++++++++++++++++ .../src/services/tools/toolDefinitions.ts | 2 + 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/frontend/src/services/toolRegistry.ts b/frontend/src/services/toolRegistry.ts index 6531807..08d0ea4 100644 --- a/frontend/src/services/toolRegistry.ts +++ b/frontend/src/services/toolRegistry.ts @@ -143,7 +143,7 @@ function getToolConfigs(): Map { const categoryTools: Record = { global: ['get_current_page', 'navigate_to', 'list_available_tools', 'activate_tool', 'deactivate_tool', 'pin_tool', 'page_refresh'], canvas: ['render_html', 'render_vue_component', 'move_window', 'resize_window', 'close_window', 'list_windows', 'inspect_window', 'get_canvas', 'edit_canvas', 'canvas_css', 'canvas_js', 'get_canvas_css', 'save_canvas_snapshot', 'load_canvas_snapshot', 'list_canvas_snapshots', 'delete_canvas_snapshot'], - component: ['save_vue_component', 'load_vue_component', 'list_vue_components', 'delete_vue_component'], + component: ['save_vue_component', 'load_vue_component', 'list_vue_components', 'delete_vue_component', 'read_component', 'edit_component'], theme: ['get_design_tokens', 'get_active_theme', 'set_theme_variable', 'save_theme', 'list_themes', 'switch_theme', 'reset_theme'], database: ['list_tables', 'get_table_schema', 'get_table_data', 'get_database_stats', 'execute_query'], source: ['get_repo_info', 'list_repo_files', 'read_repo_file', 'search_repo_code'], diff --git a/frontend/src/services/tools/handlers/componentHandlers.ts b/frontend/src/services/tools/handlers/componentHandlers.ts index 7656918..680ef54 100644 --- a/frontend/src/services/tools/handlers/componentHandlers.ts +++ b/frontend/src/services/tools/handlers/componentHandlers.ts @@ -19,6 +19,9 @@ function removePlaceholder(container: HTMLElement) { } } +// Track which component fields have been read (for edit validation) +const readComponents = new Set() // format: "component_id:field" + export function createComponentHandlers(): ToolConfig[] { return [ { @@ -152,6 +155,104 @@ export function createComponentHandlers(): ToolConfig[] { return `Error: ${e.message}` } } + }, + { + name: 'read_component', + description: 'Lee campos especificos de un componente guardado', + category: 'component', + schema: { + type: 'object', + properties: { + id: { type: 'string', description: 'ID del componente' }, + fields: { + type: 'array', + items: { type: 'string', enum: ['template', 'setup', 'style', 'props', 'imports'] }, + description: 'Campos a leer (default: template, setup, style)' + } + }, + required: ['id'] + }, + handler: async (args: { id: string; fields?: string[] }) => { + try { + const definition = await componentsApi.getById(args.id) + if (!definition) return `Error: "${args.id}" not found` + + const fields = args.fields || ['template', 'setup', 'style'] + const validFields = ['template', 'setup', 'style', 'props', 'imports'] as const + const output: string[] = [] + + for (const field of fields) { + if (!validFields.includes(field as any)) continue + const value = definition[field as keyof typeof definition] + readComponents.add(`${args.id}:${field}`) + + if (field === 'props' || field === 'imports') { + const arr = value as string[] | undefined + output.push(`--- ${field} ---\n${arr?.length ? JSON.stringify(arr) : '(empty)'}`) + } else { + const str = (value as string) || '' + output.push(`--- ${field} (${str.length}) ---\n${str || '(empty)'}`) + } + } + + return output.join('\n\n') + } catch (e: any) { + return `Error: ${e.message}` + } + } + }, + { + name: 'edit_component', + description: 'Edita un campo de componente con reemplazo de strings (requiere read_component previo)', + category: 'component', + schema: { + type: 'object', + properties: { + id: { type: 'string', description: 'ID del componente' }, + field: { type: 'string', enum: ['template', 'setup', 'style'], description: 'Campo a editar' }, + old_string: { type: 'string', description: 'Texto a reemplazar (debe ser unico en el campo)' }, + new_string: { type: 'string', description: 'Texto de reemplazo' }, + replace_all: { type: 'boolean', description: 'Reemplazar todas las ocurrencias (default: false)' } + }, + required: ['id', 'field', 'old_string', 'new_string'] + }, + handler: async (args: { id: string; field: string; old_string: string; new_string: string; replace_all?: boolean }) => { + try { + if (!readComponents.has(`${args.id}:${args.field}`)) { + return `Error: read_component "${args.id}" "${args.field}" first` + } + + const definition = await componentsApi.getById(args.id) + if (!definition) return `Error: "${args.id}" not found` + + const currentValue = (definition[args.field as keyof typeof definition] as string) || '' + + let count = 0 + let pos = 0 + while ((pos = currentValue.indexOf(args.old_string, pos)) !== -1) { + count++ + pos += args.old_string.length + } + + if (count === 0) return `Error: old_string not found in ${args.field}` + if (count > 1 && !args.replace_all) { + return `Error: ${count} matches found. Use replace_all:true or add context.` + } + + const newValue = args.replace_all + ? currentValue.replaceAll(args.old_string, args.new_string) + : currentValue.replace(args.old_string, args.new_string) + await componentsApi.update(args.id, { [args.field]: newValue }) + + readComponents.add(`${args.id}:${args.field}`) + + return count > 1 + ? `OK ${args.field} ${currentValue.length}→${newValue.length} (${count} replaced)` + : `OK ${args.field} ${currentValue.length}→${newValue.length}` + } catch (e: any) { + return `Error: ${e.message}` + } + } } ] } diff --git a/frontend/src/services/tools/toolDefinitions.ts b/frontend/src/services/tools/toolDefinitions.ts index f21e4fe..981950e 100644 --- a/frontend/src/services/tools/toolDefinitions.ts +++ b/frontend/src/services/tools/toolDefinitions.ts @@ -40,6 +40,8 @@ export const ALL_TOOL_METAS: ToolMeta[] = [ { name: 'load_vue_component', description: 'Carga y renderiza un componente guardado', category: 'component' }, { name: 'list_vue_components', description: 'Lista componentes guardados', category: 'component' }, { name: 'delete_vue_component', description: 'Elimina un componente', category: 'component' }, + { name: 'read_component', description: 'Lee campos especificos de un componente guardado', category: 'component' }, + { name: 'edit_component', description: 'Edita un campo de componente con reemplazo de strings (requiere lectura previa)', category: 'component' }, // Theme tools { name: 'get_design_tokens', description: 'Obtiene los design tokens del tema activo', category: 'theme' },