// MCP Server para PrinterCentral // Permite a agentes de IA gestionar impresoras y templates import { getAllTemplates, getTemplateById, createTemplate, updateTemplate, resolveVariables } from './templates' import type { Operation } from './templates' import { getAllPrinters, getSelectedPrinter, getPrinterById } from './printers' import { buildFromOperations } from './eposBuilder' import { buildSoapEnvelope, sendToPrinter, parsePrinterResponse } from './printer' // Definición de las tools MCP export const MCP_TOOLS = [ { name: 'printercentral_list_templates', description: 'Lista todos los templates de impresión disponibles. Retorna id, nombre, descripción (con instrucciones de uso) y variables requeridas para cada template.', inputSchema: { type: 'object' as const, properties: {}, required: [] as string[] } }, { name: 'printercentral_list_printers', description: 'Lista todas las impresoras configuradas. Retorna id, nombre, host e indica cuál es la impresora por defecto.', inputSchema: { type: 'object' as const, properties: {}, required: [] as string[] } }, { name: 'printercentral_print_template', description: 'Imprime un template guardado con variables resueltas. Usa list_templates primero para ver los templates disponibles y sus variables.', inputSchema: { type: 'object' as const, properties: { templateId: { type: 'string', description: 'ID del template a imprimir' }, variables: { type: 'object', description: 'Objeto con las variables a resolver. Las claves son los nombres de las variables definidas en el template.', additionalProperties: { type: 'string' } }, printerId: { type: 'string', description: 'ID de la impresora a usar. Si no se especifica, usa la impresora por defecto.' } }, required: ['templateId'] } }, { name: 'printercentral_print_raw', description: `Imprime operaciones ePOS directamente. Útil para impresiones personalizadas sin crear un template. FORMATO DE OPERACIONES (para impresora TM-U220, max 40 caracteres por línea): - { op: "text", value: "texto" } - Imprime texto - { op: "feed", lines: N } - Avanza N líneas (usar entre elementos para legibilidad) - { op: "cut" } - Corta el papel - { op: "align", align: "left|center|right" } - Alinea el texto - { op: "style", bold: true/false, underline: true/false, width: 1-2, height: 1-2 } - Estilo de texto EJEMPLO: [ { "op": "align", "align": "center" }, { "op": "style", "bold": true, "width": 2, "height": 2 }, { "op": "text", "value": "TITULO" }, { "op": "style", "bold": false, "width": 1, "height": 1 }, { "op": "feed", "lines": 2 }, { "op": "text", "value": "Contenido aquí" }, { "op": "feed", "lines": 4 }, { "op": "cut" } ] REGLAS IMPORTANTES: - Max 40 caracteres por línea (20 con width:2) - Usar solo caracteres ASCII (evitar unicode especial) - Siempre terminar con feed y cut`, inputSchema: { type: 'object' as const, properties: { operations: { type: 'array', description: 'Array de operaciones ePOS a ejecutar', items: { type: 'object' } }, variables: { type: 'object', description: 'Variables a resolver en el texto. Usa sintaxis {{nombreVariable}} en los valores de text.', additionalProperties: { type: 'string' } }, printerId: { type: 'string', description: 'ID de la impresora a usar. Si no se especifica, usa la impresora por defecto.' } }, required: ['operations'] } }, { name: 'printercentral_create_template', description: 'Crea un nuevo template de impresión. El template queda guardado para uso futuro.', inputSchema: { type: 'object' as const, properties: { name: { type: 'string', description: 'Nombre del template' }, description: { type: 'string', description: 'Descripción del template. IMPORTANTE: Incluir instrucciones de uso y explicación de cada variable para que otros agentes sepan cómo usarlo.' }, operations: { type: 'array', description: 'Array de operaciones ePOS. Usa {{variable}} para definir variables.', items: { type: 'object' } } }, required: ['name', 'operations'] } }, { name: 'printercentral_update_template', description: 'Actualiza un template existente. Solo se actualizan los campos proporcionados.', inputSchema: { type: 'object' as const, properties: { templateId: { type: 'string', description: 'ID del template a actualizar' }, name: { type: 'string', description: 'Nuevo nombre del template' }, description: { type: 'string', description: 'Nueva descripción del template' }, operations: { type: 'array', description: 'Nuevas operaciones del template', items: { type: 'object' } } }, required: ['templateId'] } } ] // Handlers para cada tool export async function handleToolCall(toolName: string, args: Record): Promise<{ content: Array<{ type: string; text: string }> }> { switch (toolName) { case 'printercentral_list_templates': { const templates = await getAllTemplates() const result = templates.map(t => ({ id: t.id, name: t.name, description: t.description, variables: t.variables, createdAt: t.createdAt, updatedAt: t.updatedAt })) return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } } case 'printercentral_list_printers': { const printers = await getAllPrinters() const selected = await getSelectedPrinter() const result = printers.map(p => ({ id: p.id, name: p.name, host: p.host, deviceId: p.deviceId, isDefault: p.isDefault, isSelected: selected?.id === p.id })) return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } } case 'printercentral_print_template': { const { templateId, variables = {}, printerId } = args // Obtener template const template = await getTemplateById(templateId) if (!template) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: `Template no encontrado: ${templateId}` }) }] } } // Resolver variables const resolvedOps = resolveVariables(template.operations, variables) // Construir XML const inner = buildFromOperations(resolvedOps) const soap = buildSoapEnvelope(inner) // Obtener impresora const printer = printerId ? await getPrinterById(printerId) : await getSelectedPrinter() if (!printer) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'No hay impresora configurada' }) }] } } // Enviar a impresora try { const result = await sendToPrinter(soap, printer.host, printer.deviceId, printer.timeout) const { success, code } = parsePrinterResponse(result.data) return { content: [{ type: 'text', text: JSON.stringify({ ok: success, templateName: template.name, printerUsed: { id: printer.id, name: printer.name }, code }) }] } } catch (err: any) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: err.message }) }] } } } case 'printercentral_print_raw': { const { operations, variables = {}, printerId } = args if (!operations || !Array.isArray(operations) || operations.length === 0) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'operations es requerido y debe ser un array no vacío' }) }] } } // Resolver variables const resolvedOps = resolveVariables(operations as Operation[], variables) // Construir XML const inner = buildFromOperations(resolvedOps) const soap = buildSoapEnvelope(inner) // Obtener impresora const printer = printerId ? await getPrinterById(printerId) : await getSelectedPrinter() if (!printer) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'No hay impresora configurada' }) }] } } // Enviar a impresora try { const result = await sendToPrinter(soap, printer.host, printer.deviceId, printer.timeout) const { success, code } = parsePrinterResponse(result.data) return { content: [{ type: 'text', text: JSON.stringify({ ok: success, printerUsed: { id: printer.id, name: printer.name }, code }) }] } } catch (err: any) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: err.message }) }] } } } case 'printercentral_create_template': { const { name, description, operations } = args if (!name || !operations) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'name y operations son requeridos' }) }] } } try { const template = await createTemplate({ name, description: description || '', operations }) return { content: [{ type: 'text', text: JSON.stringify({ ok: true, template: { id: template.id, name: template.name, variables: template.variables } }) }] } } catch (err: any) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: err.message }) }] } } } case 'printercentral_update_template': { const { templateId, name, description, operations } = args if (!templateId) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: 'templateId es requerido' }) }] } } const updateData: any = {} if (name !== undefined) updateData.name = name if (description !== undefined) updateData.description = description if (operations !== undefined) updateData.operations = operations try { const template = await updateTemplate(templateId, updateData) if (!template) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: `Template no encontrado: ${templateId}` }) }] } } return { content: [{ type: 'text', text: JSON.stringify({ ok: true, template: { id: template.id, name: template.name, variables: template.variables } }) }] } } catch (err: any) { return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: err.message }) }] } } } default: return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: `Tool desconocida: ${toolName}` }) }] } } }