Skip to main content
Glama

MCP Pedidos

by reyesbho
index.ts19.6 kB
#!/usr/bin/env node import { config } from 'dotenv'; // Detectar si se pasa el argumento 'production' para cargar el archivo .env.production const envFile = process.argv.includes('production') ? '.env.production' : '.env'; config({ path: envFile }); import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { v4 as uuidv4 } from 'uuid'; import { API_BASE_URL, API_EMAIL, authenticate, fetchGetPedidos, fetchGetProductos, fetchGetSizes } from './services/services.js'; import { Pagination, Pedido, Producto, Size } from './interfaces/models.js'; import { fechaToTimestamp, findProductoByDescripcion, findSizeByDescripcion, getEndOfWeek, getStartOfWeek } from './functions/Functions.js'; import { PedidoRequest, PedidosRequest, ProductoPedidoRequest, ProductoRequest } from './interfaces/request.js'; class PedidosAPI { private baseUrl: string; private authToken: string | null = null; private productosCache: Producto[] | null = null; private sizesCache: Size[] | null = null; constructor() { this.baseUrl = API_BASE_URL; } /** * Obtiene todos los productos activos */ async getProductos(): Promise<Producto[]> { if (this.productosCache) { return this.productosCache; } // Asegurar que estamos autenticados if (!this.authToken) { this.authToken = await authenticate(); if (!this.authToken) { throw new Error("No se pudo autenticar para obtener productos"); } } this.productosCache = await fetchGetProductos(this.authToken); return this.productosCache; } /** * Obtiene todos los tamaños disponibles */ async getSizes(): Promise<Size[]> { if (this.sizesCache) { return this.sizesCache; } // Asegurar que estamos autenticados if (!this.authToken) { this.authToken = await authenticate(); if (!this.authToken) { throw new Error("No se pudo autenticar para obtener tamaños"); } } this.sizesCache = await fetchGetSizes(this.authToken); return this.sizesCache; } /** * Obtiene todos los tamaños disponibles */ async getPedidos(pedidosRequest:PedidosRequest): Promise<Pedido[]> { // Asegurar que estamos autenticados if (!this.authToken) { this.authToken = await authenticate(); if (!this.authToken) { throw new Error("No se pudo autenticar para obtener tamaños"); } } if (!pedidosRequest) { return []; } let { fechaInicio, fechaFin, estatus } = pedidosRequest; if (!estatus) { estatus = 'ALL'; } if (!fechaInicio && !fechaFin) { fechaInicio = getStartOfWeek().toISOString().split('T')[0]; fechaFin = getEndOfWeek().toISOString().split('T')[0]; }else if(fechaInicio && !fechaFin) { fechaFin = fechaInicio } const pagination:Pagination = { pageSize: 150,page: 0, totalItems:0 }; // Ajusta el tamaño de página según tus necesidades const pedidos = await fetchGetPedidos( this.authToken, estatus ?? null, fechaInicio ?? null, fechaFin ?? null, pagination ); return pedidos; } /** * Transforma los parámetros de entrada al formato JSON requerido */ async crearPedidoJSON( fechaEntrega: string, lugarEntrega: string, cliente: string, productosPedido: ProductoPedidoRequest[] ): Promise<PedidoRequest> { console.log("🔄 Transformando pedido a JSON..."); // Asegurar que tenemos productos y tamaños cargados const productosForSearch = await this.getProductos(); const sizesForSearch = await this.getSizes(); const productosJSON: ProductoRequest[] = []; let total = 0; for (const productoPedido of productosPedido) { // Buscar el producto const productoObj = findProductoByDescripcion(productosForSearch,productoPedido.producto); if (!productoObj) { throw new Error(`Producto no encontrado: ${productoPedido.producto}`); } // Buscar el tamaño const sizeObj = findSizeByDescripcion(sizesForSearch,productoPedido.tamaño); if (!sizeObj) { throw new Error(`Tamaño no encontrado: ${productoPedido.tamaño}`); } // Generar ID único para el producto en el pedido const productoId = uuidv4(); const precio = Number(productoPedido.precio); const cantidad = Number(productoPedido.cantidad); const productoJSON: ProductoRequest = { precio: precio, producto: { id: productoObj.id, imagen: productoObj.imagen || '', descripcion: productoObj.descripcion }, id: productoId, caracteristicas: productoPedido.caracteristicas || [], size: { descripcion: sizeObj.descripcion, id: sizeObj.id }, cantidad: cantidad }; productosJSON.push(productoJSON); total += precio * cantidad; } // Crear el JSON final const pedidoJSON: PedidoRequest = { total: total, lugarEntrega: lugarEntrega, productos: productosJSON, cliente: cliente, fechaEntrega: fechaToTimestamp(fechaEntrega), registradoPor: API_EMAIL }; console.log("✅ Pedido JSON creado exitosamente"); return pedidoJSON; } /** * Registra el pedido en la API */ async registrarPedido(pedidoJSON: PedidoRequest): Promise<any> { console.log("📝 Registrando pedido..."); if (!this.authToken) { this.authToken = await authenticate(); if (!this.authToken) { throw new Error("No se pudo autenticar"); } } try { const response = await fetch(`${this.baseUrl}/api/pedidos`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Cookie': `access_token=${this.authToken}` }, body: JSON.stringify(pedidoJSON) }); if (response.ok) { const resultado = await response.json(); console.log("✅ Pedido registrado exitosamente"); return resultado; } else if (response.status === 401) { // Token expirado, intentar reautenticar console.log("🔄 Token expirado, reautenticando..."); this.authToken = null; this.authToken = await authenticate(); if (this.authToken) { // Reintentar la petición return await this.registrarPedido(pedidoJSON); } else { throw new Error("No se pudo reautenticar"); } } else { const errorText = await response.text(); console.error(`❌ Error registrando pedido: ${response.status} - ${errorText}`); return { error: `HTTP ${response.status}: ${errorText}` }; } } catch (error) { console.error(`❌ Error registrando pedido: ${error}`); return { error: String(error) }; } } /** * Limpia el caché de productos y tamaños */ clearCache(): void { this.productosCache = null; this.sizesCache = null; console.log("🗑️ Caché limpiado"); } /** * Invalida el token y limpia caché (útil cuando hay problemas de autenticación) */ invalidateAuth(): void { this.authToken = null; this.clearCache(); console.log("🔓 Autenticación invalidada y caché limpiado"); } /** * Obtiene el estado de autenticación */ getAuthStatus(): { authenticated: boolean; tokenAvailable: boolean } { return { authenticated: this.authToken !== null, tokenAvailable: Boolean(this.authToken) }; } } // Inicializar la API const api = new PedidosAPI(); // Crear el servidor MCP const server = new Server( { name: "pedidos-mcp", version: "1.0.0", }, { capabilities: { resources: {}, tools: {}, }, } ); // Manejar lista de recursos server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: "pedidos://productos", name: "Productos disponibles", description: "Lista de todos los productos activos disponibles para pedidos", mimeType: "application/json", }, { uri: "pedidos://sizes", name: "Tamaños disponibles", description: "Lista de todos los tamaños disponibles para productos", mimeType: "application/json", }, { uri: "pedidos://auth-status", name: "Estado de autenticación", description: "Información sobre el estado actual de autenticación", mimeType: "application/json", }, { uri: "pedidos://pedidos", name: "Pedidos registrados", description: "Lista de todos los pedidos registrados (parámetros opcionales: fechaInicio, fechaFin, estatus)", mimeType: "application/json", } ], }; }); // Manejar lectura de recursos server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; if (!uri.startsWith("pedidos://")) { throw new Error(`Esquema no soportado: ${uri}`); } const path = uri.replace("pedidos://", ""); switch (path) { case "productos": const productos = await api.getProductos(); return { contents: [ { uri: uri, mimeType: "application/json", text: JSON.stringify(productos, null, 2), }, ], }; case "sizes": const sizes = await api.getSizes(); return { contents: [ { uri: uri, mimeType: "application/json", text: JSON.stringify(sizes, null, 2), }, ], }; case "auth-status": const status = api.getAuthStatus(); return { contents: [ { uri: uri, mimeType: "application/json", text: JSON.stringify(status, null, 2), }, ], }; case "pedidos": { // Permitir parámetros opcionales por query string: fechaInicio, fechaFin, estatus let fechaInicio = typeof request.params.fechaInicio === 'string' ? request.params.fechaInicio : undefined; let fechaFin = typeof request.params.fechaFin === 'string' ? request.params.fechaFin : undefined; let estatus = typeof request.params.estatus === 'string' ? request.params.estatus : undefined; const pedidosRequest = { fechaInicio, fechaFin, estatus }; const pedidos = await api.getPedidos(pedidosRequest); return { contents: [ { uri: uri, mimeType: "application/json", text: JSON.stringify(pedidos, null, 2), }, ], }; } default: throw new Error(`Recurso no encontrado: ${path}`); } }); // Manejar lista de herramientas server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "crear_pedido", description: "Crea un nuevo pedido transformando los parámetros al formato JSON requerido y registrándolo en la API", inputSchema: { type: "object", properties: { fechaEntrega: { type: "string", description: "Fecha de entrega del pedido (formatos: DD-MM-YYYY)" }, lugarEntrega: { type: "string", description: "Lugar donde se entregará el pedido" }, cliente: { type: "string", description: "Nombre del cliente" }, productos: { type: "array", description: "Lista de productos a incluir en el pedido", items: { type: "object", properties: { producto: { type: "string", description: "Nombre/descripción del producto" }, cantidad: { type: "integer", description: "Cantidad del producto" }, precio: { type: "number", description: "Precio unitario del producto" }, tamaño: { type: "string", description: "Tamaño del producto" }, caracteristicas: { type: "array", items: { type: "string" }, description: "Lista de características especiales del producto" } }, required: ["producto", "cantidad", "precio", "tamaño"] } } }, required: ["fechaEntrega", "lugarEntrega", "cliente", "productos"] } }, { name: "consultar_pedidos", description: "Consulta los pedidos registrados. Parámetros opcionales: fechaInicio, fechaFin, estatus.", inputSchema: { type: "object", properties: { fechaInicio: { type: "string", description: "Fecha de inicio (DD-MM-YYYY)" }, fechaFin: { type: "string", description: "Fecha de fin (DD-MM-YYYY)" }, estatus: { type: "string", description: "Estatus del pedido (ejemplo: ALL, BACKLOG, CANCELED, INCOMPLETE, DELETE)" } }, required: [] } }, { name: "autenticar", description: "Autentica con la API para obtener el token de acceso", inputSchema: { type: "object", properties: {}, additionalProperties: false } }, { name: "buscar_producto", description: "Busca un producto por su descripción", inputSchema: { type: "object", properties: { descripcion: { type: "string", description: "Descripción del producto a buscar" } }, required: ["descripcion"] } }, { name: "buscar_todos_productos", description: "Busca todos los productos disponibles", inputSchema: { } }, { name: "buscar_tamaño", description: "Busca un tamaño por su descripción", inputSchema: { type: "object", properties: { descripcion: { type: "string", description: "Descripción del tamaño a buscar" } }, required: ["descripcion"] } }, { name: "limpiar_cache", description: "Limpia el caché de productos y tamaños para forzar una actualización", inputSchema: { type: "object", properties: {}, additionalProperties: false } }, { name: "invalidar_auth", description: "Invalida la autenticación actual y limpia caché (útil cuando hay problemas de token)", inputSchema: { type: "object", properties: {}, additionalProperties: false } } ], }; }); // Manejar llamadas a herramientas server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "crear_pedido": { const { fechaEntrega, lugarEntrega, cliente, productos } = args as { fechaEntrega: string; lugarEntrega: string; cliente: string; productos: ProductoPedidoRequest[]; }; // Crear el JSON del pedido const pedidoJSON = await api.crearPedidoJSON( fechaEntrega, lugarEntrega, cliente, productos ); // Registrar el pedido const resultado = await api.registrarPedido(pedidoJSON); const response = { pedido_creado: pedidoJSON, resultado_api: resultado }; return { content: [ { type: "text", text: JSON.stringify(response, null, 2) } ] }; } case "consultar_pedidos": { // Permitir parámetros opcionales: fechaInicio, fechaFin, estatus const { fechaInicio, fechaFin, estatus } = args as { fechaInicio?: string; fechaFin?: string; estatus?: string }; const pedidosRequest = { fechaInicio, fechaFin, estatus }; const pedidos = await api.getPedidos(pedidosRequest); return { content: [ { type: "text", text: JSON.stringify(pedidos, null, 2) } ] }; } case "autenticar": { const success = await authenticate(); return { content: [ { type: "text", text: `Autenticación: ${success ? 'exitosa' : 'fallida'}` } ] }; } case "buscar_producto": { const { descripcion: descripcionProducto } = args as { descripcion: string }; const productosForSearch = await api.getProductos(); const producto = findProductoByDescripcion(productosForSearch, descripcionProducto); return { content: [ { type: "text", text: producto ? JSON.stringify(producto, null, 2) : `Producto no encontrado: ${descripcionProducto}` } ] }; } case "buscar_todos_productos": { const productosArray = await api.getProductos(); return { content: [ { type: "text", text: productosArray ? JSON.stringify(productosArray, null, 2) : `No hay productos disponibles` } ] }; } case "buscar_tamaño": { const { descripcion: descripcionTamaño } = args as { descripcion: string }; const sizesForSearch = await api.getSizes(); const size = findSizeByDescripcion(sizesForSearch,descripcionTamaño); return { content: [ { type: "text", text: size ? JSON.stringify(size, null, 2) : `Tamaño no encontrado: ${descripcionTamaño}` } ] }; } case "limpiar_cache": { api.clearCache(); return { content: [ { type: "text", text: "Caché limpiado exitosamente" } ] }; } case "invalidar_auth": { api.invalidateAuth(); return { content: [ { type: "text", text: "Autenticación invalidada y caché limpiado exitosamente" } ] }; } default: throw new Error(`Herramienta no conocida: ${name}`); } } catch (error) { return { content: [ { type: "text", text: `Error ejecutando ${name}: ${error}` } ] }; } }); // Función principal const transport = new StdioServerTransport(); await server.connect(transport); console.log("🚀 Servidor MCP de Pedidos iniciado"); console.log("user: ", API_EMAIL); console.log("API Base URL: ", API_BASE_URL);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/reyesbho/mcp-moments'

If you have feedback or need assistance with the MCP directory API, please join our Discord server