server-fixed.js•7.71 kB
#!/usr/bin/env node
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
// Get __dirname equivalent in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Importar módulos que implementan la Guía de Trabajo Fundamental
import { validator } from './src/utils/validator.js';
import { dataLoader } from './src/data/dataLoader.js';
import { businessCalculator } from './src/business/calculator.js';
// Rutas a los datos
const dataPath = path.join(__dirname, 'data');
const webAppPath = path.join(__dirname, '../treepod-financial-webapp');
const competitorPath = path.join(__dirname, '../treepod-competitive-agent/reports');
/**
* 🏕️ TreePod Glamping - Agente Financiero MCP
* Agente inteligente que responde consultas sobre el negocio TreePod
* directamente dentro de Claude Desktop.
*/
const server = new McpServer({
name: 'treepod-financial',
version: '1.0.0',
});
// --- Herramienta: analyze_finances ---
server.registerTool(
'analyze_finances',
{
title: 'Analizar finanzas',
description: 'Analiza las finanzas actuales de TreePod Glamping con métricas clave basado en datos reales',
inputSchema: z.object({
period: z.string().default('current'),
}),
},
async ({ period }) => {
validator.log('info', `Iniciando análisis financiero para período: ${period}`);
try {
// Usar módulo de cálculo sin hardcodeo
const result = await businessCalculator.analyzeFinancialMetrics(period);
validator.log('info', 'Análisis financiero completado exitosamente');
return result;
} catch (error) {
validator.log('error', `Error crítico en análisis financiero: ${error.message}`);
return validator.generateInsufficientDataResponse(
'análisis financiero',
'Error interno del sistema. Contacta al administrador.'
);
}
}
);
// --- Herramienta: calculate_tariff ---
server.registerTool(
'calculate_tariff',
{
title: 'Calcular tarifa',
description: 'Calcula tarifas para reservas según temporada, personas y canal basado en configuración real',
inputSchema: z.object({
checkin_date: z.string(),
checkout_date: z.string(),
guests: z.number().min(1).max(4),
channel: z.string().optional().default('directo'),
}),
},
async ({ checkin_date, checkout_date, guests, channel = 'directo' }) => {
validator.log('info', `Iniciando cálculo de tarifa: ${guests} huéspedes, ${checkin_date} a ${checkout_date}, canal ${channel}`);
try {
// Usar módulo de cálculo sin hardcodeo
const result = await businessCalculator.calculateTariff(checkin_date, checkout_date, guests, channel);
validator.log('info', 'Cálculo de tarifa completado exitosamente');
return result;
} catch (error) {
validator.log('error', `Error crítico en cálculo de tarifa: ${error.message}`);
return validator.generateInsufficientDataResponse(
'cálculo de tarifa',
'Error interno del sistema. Contacta al administrador.'
);
}
}
);
// --- Herramienta: get_business_status ---
server.registerTool(
'get_business_status',
{
title: 'Estado del negocio',
description: 'Estado general actual del negocio con alertas y KPIs',
inputSchema: z.object({}),
},
async () => {
try {
const financialData = await loadFinancialData();
const businessStatus = await loadBusinessStatus();
const alerts = await checkAlerts();
const revenue = financialData.ingresos_total || 5200000;
const expenses = financialData.gastos_total || 3400000;
const profit = revenue - expenses;
const margin = (profit / revenue * 100).toFixed(1);
const occupancy = businessStatus.occupancy || 67;
let overallStatus = '🟢 Excelente';
if (profit < 1500000 || occupancy < 60) {
overallStatus = '🔴 Requiere atención';
} else if (profit < 2000000 || occupancy < 70) {
overallStatus = '🟡 Bueno';
}
return {
content: [{
type: 'text',
text:
`🎯 **ESTADO GENERAL TREEPOD GLAMPING**\n\n` +
`💼 **Estado del Negocio:** ${overallStatus}\n` +
`📅 **Última actualización:** ${new Date().toLocaleDateString('es-CL')}\n\n` +
`**📊 KPIs PRINCIPALES:**\n` +
`• 💰 Ingresos: ${formatCurrency(revenue)}\n` +
`• 📈 Utilidad: ${formatCurrency(profit)} (${margin}%)\n` +
`• 🏠 Ocupación: ${occupancy}%\n` +
`• 📅 Reservas: ${financialData.reservas_totales || 18}\n\n` +
`**🚨 ALERTAS ACTIVAS:**\n` +
`${alerts.length > 0 ? alerts.map(alert => `• ${alert}`).join('\n') : '✅ Sin alertas críticas'}\n\n` +
`**🎯 PROGRESO HACIA METAS:**\n` +
`• Ingresos: ${((revenue/6000000)*100).toFixed(1)}% de $6M\n` +
`• Utilidad: ${((profit/2000000)*100).toFixed(1)}% de $2M\n` +
`• Ocupación: ${((occupancy/70)*100).toFixed(1)}% de 70%\n\n` +
`**⚡ ACCIONES RECOMENDADAS:**\n` +
`${getActionRecommendations(revenue, profit, occupancy)}`
}]
};
} catch (error) {
return { content: [{ type: 'text', text: `❌ Error obteniendo estado del negocio: ${error.message}` }] };
}
}
);
// --- Métodos auxiliares ---
async function loadFinancialData() {
try {
const filePath = path.join(webAppPath, 'sample-data.json');
const data = await fs.readFile(filePath, 'utf8');
return JSON.parse(data);
} catch (error) {
return {
ingresos_total: 5200000,
gastos_total: 3400000,
ocupacion_promedio: 67,
reservas_totales: 18
};
}
}
async function loadBusinessStatus() {
try {
const filePath = path.join(dataPath, 'inter-agent-bus.json');
const data = await fs.readFile(filePath, 'utf8');
return JSON.parse(data);
} catch (error) {
return { occupancy: 67 };
}
}
async function checkAlerts() {
const alerts = [];
const financialData = await loadFinancialData();
const revenue = financialData.ingresos_total || 5200000;
const profit = revenue - (financialData.gastos_total || 3400000);
const occupancy = financialData.ocupacion_promedio || 67;
if (revenue < 5500000) alerts.push('🟡 Ingresos por debajo de la meta');
if (profit < 1800000) alerts.push('🟡 Utilidad por debajo de la meta');
if (occupancy < 65) alerts.push('🟡 Ocupación baja - considerar promociones');
return alerts;
}
function getActionRecommendations(revenue, profit, occupancy) {
const actions = [];
if (revenue < 5500000) actions.push('1. Intensificar marketing digital y promociones');
if (profit < 1800000) actions.push('2. Revisar gastos operacionales y optimizar costos');
if (occupancy < 65) actions.push('3. Lanzar campaña de último momento para fechas disponibles');
if (actions.length === 0) actions.push('• Mantener estrategia actual - métricas positivas');
return actions.join('\n');
}
function formatCurrency(amount) {
return new Intl.NumberFormat('es-CL', { style: 'currency', currency: 'CLP', minimumFractionDigits: 0 }).format(amount);
}
// --- Iniciar el servidor MCP ---
const transport = new StdioServerTransport();
server.connect(transport).then(() => {
console.error('🏕️ TreePod Financial Agent MCP iniciado y listo para consultas');
}).catch(console.error);