server.js•12.3 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';
// 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';
/**
* 🏕️ TreePod Glamping - Agente Financiero MCP
* Agente inteligente que responde consultas sobre el negocio TreePod
* directamente dentro de Claude Desktop.
*
* ✅ IMPLEMENTA GUÍA DE TRABAJO FUNDAMENTAL:
* - Sin datos hardcodeados
* - Sin inventar información
* - Validación robusta
* - Trazabilidad completa
* - Arquitectura modular
*/
const server = new McpServer({
name: 'treepod-financial',
version: '1.0.0',
});
// --- Herramienta: analyze_finances ---
// ✅ IMPLEMENTA GUÍA DE TRABAJO FUNDAMENTAL: Sin hardcodeo, validación, trazabilidad
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 ---
// ✅ IMPLEMENTA GUÍA DE TRABAJO FUNDAMENTAL: Sin hardcodeo, validación robusta
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: check_occupancy ---
// ✅ IMPLEMENTA GUÍA DE TRABAJO FUNDAMENTAL: Sin hardcodeo, datos reales
server.registerTool(
'check_occupancy',
{
title: 'Verificar ocupación',
description: 'Verifica estado de ocupación actual y futura de los domos basado en datos reales',
inputSchema: z.object({
date_range: z.string().default('today'),
}),
},
async ({ date_range = 'today' }) => {
validator.log('info', `Iniciando verificación de ocupación para período: ${date_range}`);
try {
// Cargar datos reales sin hardcodeo
const businessData = await dataLoader.loadBusinessStatus();
const domosStatus = await dataLoader.loadDomosStatus();
// Validar que tenemos datos suficientes
if (!businessData && !domosStatus) {
return validator.generateInsufficientDataResponse(
'datos de ocupación',
'No se pudo acceder a los datos de estado del negocio ni de domos'
);
}
if (!domosStatus) {
return validator.generateInsufficientDataResponse(
'estado de domos',
'No se pudo acceder al estado actual de los domos'
);
}
const result = await businessCalculator.analyzeOccupancy(businessData, domosStatus, date_range);
validator.log('info', 'Verificación de ocupación completada exitosamente');
return result;
} catch (error) {
validator.log('error', `Error crítico en verificación de ocupación: ${error.message}`);
return validator.generateInsufficientDataResponse(
'verificación de ocupación',
'Error interno del sistema. Contacta al administrador.'
);
}
}
);
// --- Herramienta: compare_competition ---
// ✅ IMPLEMENTA GUÍA DE TRABAJO FUNDAMENTAL: Sin hardcodeo, datos reales
server.registerTool(
'compare_competition',
{
title: 'Comparar competencia',
description: 'Compara precios y servicios con la competencia basado en datos reales',
inputSchema: z.object({
analysis_type: z.string().default('all'),
}),
},
async ({ analysis_type = 'all' }) => {
validator.log('info', `Iniciando análisis de competencia tipo: ${analysis_type}`);
try {
// Cargar datos reales de competencia
const competitionData = await dataLoader.loadCompetitionData();
if (!competitionData) {
return validator.generateInsufficientDataResponse(
'datos de competencia',
'El sistema de inteligencia competitiva no ha generado reportes recientes. Ejecuta el agente competitivo para obtener datos actualizados.'
);
}
const result = await businessCalculator.analyzeCompetition(competitionData, analysis_type);
validator.log('info', 'Análisis de competencia completado exitosamente');
return result;
} catch (error) {
validator.log('error', `Error crítico en análisis de competencia: ${error.message}`);
return validator.generateInsufficientDataResponse(
'análisis de competencia',
'Error interno del sistema. Contacta al administrador.'
);
}
}
);
// --- Herramienta: generate_report ---
// ✅ IMPLEMENTA GUÍA DE TRABAJO FUNDAMENTAL: Sin hardcodeo, datos reales
server.registerTool(
'generate_report',
{
title: 'Generar reporte',
description: 'Genera reportes ejecutivos del negocio basado en datos reales',
inputSchema: z.object({
report_type: z.string().default('monthly'),
format: z.string().default('summary'),
}),
},
async ({ report_type = 'monthly', format = 'summary' }) => {
validator.log('info', `Iniciando generación de reporte tipo: ${report_type}, formato: ${format}`);
try {
// Validar parámetros de entrada
const inputValidation = validator.validateUserInput({
report_type,
format
}, {
report_type: { required: true, type: 'string', enum: ['monthly', 'occupancy', 'financial', 'competition'] },
format: { required: true, type: 'string', enum: ['summary', 'detailed'] }
});
if (!inputValidation.valid) {
return validator.generateInsufficientDataResponse(
'parámetros de reporte',
`Errores: ${inputValidation.errors.join(', ')}`
);
}
const result = await businessCalculator.generateReport(report_type, format);
validator.log('info', 'Reporte generado exitosamente');
return result;
} catch (error) {
validator.log('error', `Error crítico en generación de reporte: ${error.message}`);
return validator.generateInsufficientDataResponse(
'generación de reporte',
'Error interno del sistema. Contacta al administrador.'
);
}
}
);
// --- Herramienta: get_business_status ---
// ✅ IMPLEMENTA GUÍA DE TRABAJO FUNDAMENTAL: Sin hardcodeo, datos reales
server.registerTool(
'get_business_status',
{
title: 'Estado del negocio',
description: 'Estado general actual del negocio con alertas y KPIs basado en datos reales',
inputSchema: z.object({
format: z.string().optional().default('summary')
}),
},
async () => {
validator.log('info', 'Iniciando consulta de estado general del negocio');
try {
const result = await businessCalculator.getBusinessStatus();
validator.log('info', 'Estado del negocio obtenido exitosamente');
return result;
} catch (error) {
validator.log('error', `Error crítico obteniendo estado del negocio: ${error.message}`);
return validator.generateInsufficientDataResponse(
'estado del negocio',
'Error interno del sistema. Contacta al administrador.'
);
}
}
);
// --- Herramienta: optimize_pricing ---
// ✅ IMPLEMENTA GUÍA DE TRABAJO FUNDAMENTAL: Sin hardcodeo, datos reales
server.registerTool(
'optimize_pricing',
{
title: 'Optimizar precios',
description: 'Sugiere optimizaciones de precios basado en competencia y ocupación usando datos reales',
inputSchema: z.object({
strategy: z.string().default('maximize_revenue'),
}),
},
async ({ strategy = 'maximize_revenue' }) => {
validator.log('info', `Iniciando optimización de precios con estrategia: ${strategy}`);
try {
// Validar parámetros de entrada
const inputValidation = validator.validateUserInput({
strategy
}, {
strategy: { required: true, type: 'string', enum: ['maximize_revenue', 'maximize_occupancy', 'balanced', 'competitive'] }
});
if (!inputValidation.valid) {
return validator.generateInsufficientDataResponse(
'estrategia de optimización',
`Errores: ${inputValidation.errors.join(', ')}`
);
}
const result = await businessCalculator.optimizePricing(strategy);
validator.log('info', 'Optimización de precios completada exitosamente');
return result;
} catch (error) {
validator.log('error', `Error crítico en optimización de precios: ${error.message}`);
return validator.generateInsufficientDataResponse(
'optimización de precios',
'Error interno del sistema. Contacta al administrador.'
);
}
}
);
// --- Herramienta: predict_revenue ---
// ✅ IMPLEMENTA GUÍA DE TRABAJO FUNDAMENTAL: Sin hardcodeo, datos reales
server.registerTool(
'predict_revenue',
{
title: 'Predecir ingresos',
description: 'Predice ingresos futuros basado en datos históricos y tendencias reales',
inputSchema: z.object({
period: z.string().default('next_month'),
}),
},
async ({ period = 'next_month' }) => {
validator.log('info', `Iniciando predicción de ingresos para período: ${period}`);
try {
// Validar parámetros de entrada
const inputValidation = validator.validateUserInput({
period
}, {
period: { required: true, type: 'string', enum: ['next_week', 'next_month', 'next_quarter', 'next_semester', 'next_year'] }
});
if (!inputValidation.valid) {
return validator.generateInsufficientDataResponse(
'período de predicción',
`Errores: ${inputValidation.errors.join(', ')}`
);
}
const result = await businessCalculator.predictRevenue(period);
validator.log('info', 'Predicción de ingresos completada exitosamente');
return result;
} catch (error) {
validator.log('error', `Error crítico en predicción de ingresos: ${error.message}`);
return validator.generateInsufficientDataResponse(
'predicción de ingresos',
'Error interno del sistema. Contacta al administrador.'
);
}
}
);
// Iniciar el servidor
const transport = new StdioServerTransport();
server.connect(transport).then(() => {
console.error('🏕️ TreePod Financial MCP - Servidor de prueba iniciado');
}).catch((error) => {
console.error('❌ Error iniciando servidor:', error);
process.exit(1);
});