From 546c654111620c3bff5fabb0be61edc452abaf9c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 7 Jun 2025 05:49:44 +0000 Subject: [PATCH 1/2] Refactor MCP resources to tools, add chat deletion This commit implements two main changes based on your feedback: 1. Refactors all Model Context Protocol (MCP) resources into tools: - In `whatsappIntegration.js`: - `whatsapp.chat` (resource) -> `whatsapp.list-chats` (tool) & `whatsapp.get-chat-details` (tool) - `whatsapp.contact` (resource) -> `whatsapp.get-contact-details` (tool) - `whatsapp.blocklist` (resource) -> `whatsapp.get-blocklist` (tool) - In `conversationIntegration.js`: - `conversation` (resource) -> `conversation.list-conversations` (tool) & `conversation.get-conversation-details` (tool) This change aligns with current Gemini SDK capabilities for MCP. 2. Implements chat deletion functionality: - In `whatsapp-router`: - Added `deleteChat` function to `whatsappClient.ts`. This function calls an assumed OpenWA endpoint (`POST /deleteChat`) and will require verification against the actual OpenWA API. - Added a `DELETE /chats/:chatId` route to `whatsappActions.ts` that utilizes the new `deleteChat` client function. - In `conversation-layer-mcp`: - Added a new `whatsapp.delete-chat` tool to `whatsappIntegration.js`. This tool calls the new `DELETE /chats/:chatId` endpoint in `whatsapp-router`. Additionally, the existing `conversation.delete` MCP tool was verified to be correctly implemented. --- docker-compose.yml | 14 +- mcp/createServer.js | 9 +- mcp/lib/api.js | 7 +- mcp/modules/conversationIntegration.js | 69 ++++++ mcp/modules/tareas.js | 111 --------- mcp/modules/whatsappIntegration.js | 211 ++++++++++++++++++ whatsapp-router/src/routes/whatsappActions.ts | 23 ++ whatsapp-router/src/whatsappClient.ts | 28 +++ 8 files changed, 355 insertions(+), 117 deletions(-) create mode 100644 mcp/modules/conversationIntegration.js delete mode 100644 mcp/modules/tareas.js create mode 100644 mcp/modules/whatsappIntegration.js diff --git a/docker-compose.yml b/docker-compose.yml index 1594146..07fe141 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,12 +45,24 @@ services: environment: - PORT=8001 - GEMINI_API_KEY= AIzaSyA9fI1mron-NVgghygu7B4sco7t6raXB8M - - MCP_URL= http:planilla-mcp:5000/mcp + - MCP_URL=http://conversation-layer-mcp:5000/mcp ports: - "8001:8001" networks: - principal + conversation-layer-mcp: + build: ./mcp + image: gitea.interno.com/nucleo000/conversation-layer-mcp:latest + container_name: conversation-layer-mcp + environment: + - PORT=5000 + - WHATSAPP_ROUTER_URL=http://whatsapp-router:3001 + ports: + - "5000:5000" + networks: + - principal + volumes: nucleo_whatsapp_sessions: diff --git a/mcp/createServer.js b/mcp/createServer.js index 293cfba..5acf1c9 100644 --- a/mcp/createServer.js +++ b/mcp/createServer.js @@ -1,11 +1,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - -import registerTareas from "./modules/tareas.js"; +import registerWhatsappIntegration from "./modules/whatsappIntegration.js"; +import registerConversationIntegration from "./modules/conversationIntegration.js"; export function createServer() { - const server = new McpServer({ name: "planilla-mcp", version: "0.1.0" }); + const server = new McpServer({ name: "conversation-layer-mcp", version: "0.1.0" }); - registerTareas(server); + registerWhatsappIntegration(server); + registerConversationIntegration(server); return server; } diff --git a/mcp/lib/api.js b/mcp/lib/api.js index d7364f1..2595cfd 100644 --- a/mcp/lib/api.js +++ b/mcp/lib/api.js @@ -1,4 +1,9 @@ -export const API_BASE_URL = process.env.PLANILLA_API_URL || "http://localhost:4000"; +export const API_BASE_URL = process.env.WHATSAPP_ROUTER_URL; + +if (!API_BASE_URL) { + console.error("FATAL: WHATSAPP_ROUTER_URL environment variable is not set."); + process.exit(1); +} export async function fetchJSON(path, options = {}) { const method = options.method || "GET"; diff --git a/mcp/modules/conversationIntegration.js b/mcp/modules/conversationIntegration.js new file mode 100644 index 0000000..80bac35 --- /dev/null +++ b/mcp/modules/conversationIntegration.js @@ -0,0 +1,69 @@ +// mcp/modules/conversationIntegration.js +import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { fetchJSON } from "../lib/api.js"; + +const log = (...args) => console.log("[MCP ConversationIntegration]", ...args); // Changed log prefix + +export default function registerConversationIntegration(server) { + // --- Conversation Actions --- + + // Tool: conversation.list-conversations + server.tool( + "conversation.list-conversations", + "Retrieves a list of all conversations.", + {}, // No input parameters + async () => { + log("Tool invoked", "conversation.list-conversations"); + const result = await fetchJSON("/conversations"); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: conversation.get-conversation-details + server.tool( + "conversation.get-conversation-details", + "Retrieves details for a specific conversation by its ID.", + { + id: z.string().describe("ID of the conversation"), + }, + async (params) => { + log("Tool invoked", "conversation.get-conversation-details", params); + const result = await fetchJSON(`/conversations/${params.id}`); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: conversation.update + server.tool( + "conversation.update", + "Updates/rebuilds a conversation by its ID.", + { + id: z.string().describe("ID of the conversation to update"), + }, + async (params) => { + log("Tool invoked", "conversation.update", params); + const result = await fetchJSON(`/conversations/${params.id}/update`, { + method: "POST", + // No body needed for this specific route as per analysis + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: conversation.delete + server.tool( + "conversation.delete", + "Deletes a conversation by its ID.", + { + id: z.string().describe("ID of the conversation to delete"), + }, + async (params) => { + log("Tool invoked", "conversation.delete", params); + const result = await fetchJSON(`/conversations/${params.id}`, { + method: "DELETE", + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); +} diff --git a/mcp/modules/tareas.js b/mcp/modules/tareas.js deleted file mode 100644 index 553f8dc..0000000 --- a/mcp/modules/tareas.js +++ /dev/null @@ -1,111 +0,0 @@ -import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { z } from "zod"; -import { fetchJSON } from "../lib/api.js"; - -const log = (...args) => console.log("[MCP]", ...args); - -export default function registerTareas(server) { - server.resource("tarea-list", "tarea://list", async (uri) => { - log("Recurso solicitado", "tarea-list"); - const tareas = await fetchJSON("/api/tareas"); - return { contents: [{ uri: uri.href, text: JSON.stringify(tareas) }] }; - }); - - server.resource( - "tarea", - new ResourceTemplate("tarea://{id}", { list: undefined }), - async (uri, { id }) => { - log("Recurso solicitado", `tarea ${id}`); - const tarea = await fetchJSON(`/api/tareas/${id}`); - return { contents: [{ uri: uri.href, text: JSON.stringify(tarea) }] }; - } - ); - - server.tool( - "create-tarea", - "Crea una tarea", - { - empleado_id: z.number(), - planilla_id: z.number().optional(), - titulo: z.string(), - precio: z.number().optional(), - estado: z.string().optional(), - observacion: z.string().optional(), - fecha: z.string(), - tipo: z.string().optional(), - fecha_anulado: z.string().optional(), - creador_id: z.number().optional(), - anulador_id: z.number().optional(), - }, - async (params) => { - log("Tool invocada", "create-tarea", params); - const tarea = await fetchJSON("/api/tareas", { - method: "POST", - body: JSON.stringify(params), - }); - return { content: [{ type: "text", text: JSON.stringify(tarea) }] }; - } - ); - - server.tool( - "update-tarea", - "Actualiza una tarea", - { - id: z.number(), - empleado_id: z.number().optional(), - planilla_id: z.number().optional(), - titulo: z.string().optional(), - precio: z.number().optional(), - estado: z.string().optional(), - observacion: z.string().optional(), - fecha: z.string().optional(), - tipo: z.string().optional(), - fecha_anulado: z.string().optional(), - anulador_id: z.number().optional(), - }, - async ({ id, ...updates }) => { - log("Tool invocada", "update-tarea", { id, ...updates }); - const tarea = await fetchJSON(`/api/tareas/${id}`, { - method: "PUT", - body: JSON.stringify(updates), - }); - return { content: [{ type: "text", text: JSON.stringify(tarea) }] }; - } - ); - - server.tool( - "delete-tarea", - "Elimina una tarea", - { id: z.number() }, - async ({ id }) => { - log("Tool invocada", "delete-tarea", { id }); - await fetchJSON(`/api/tareas/${id}`, { method: "DELETE" }); - return { content: [{ type: "text", text: `Tarea ${id} eliminada` }] }; - } - ); - - server.tool( - "search-tareas", - "Busca tareas. `q` matchea id, empleado_id, planilla_id o título. Si no mandas filtros devuelve los primeros 100 registros.", - { - q: z.string().optional(), - empleado_id: z.number().optional(), - planilla_id: z.number().optional(), - estado: z.string().optional(), - titulo: z.string().optional(), - fecha_desde: z.string().optional(), - fecha_hasta: z.string().optional(), - }, - async (params) => { - log("Tool invocada", "search-tareas", params); - const qs = new URLSearchParams( - Object.entries(params) - .filter(([, v]) => v !== undefined) - .map(([k, v]) => [k, String(v)]) - ); - if (qs.toString() === "") qs.append("limit", "100"); - const tareas = await fetchJSON(`/api/tareas/search?${qs.toString()}`); - return { content: [{ type: "text", text: JSON.stringify(tareas) }] }; - } - ); -} diff --git a/mcp/modules/whatsappIntegration.js b/mcp/modules/whatsappIntegration.js new file mode 100644 index 0000000..7c42dc1 --- /dev/null +++ b/mcp/modules/whatsappIntegration.js @@ -0,0 +1,211 @@ +// mcp/modules/whatsappIntegration.js +import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { fetchJSON } from "../lib/api.js"; + +const log = (...args) => console.log("[MCP WhatsAppIntegration]", ...args); // Changed log prefix + +export default function registerWhatsappIntegration(server) { + // --- WhatsApp Actions --- + + // Tool: whatsapp.send-text + server.tool( + "whatsapp.send-text", + "Sends a text message via WhatsApp.", + { + to: z.string().describe("Recipient ID (e.g., 1234567890@c.us)"), + content: z.string().describe("Text message content"), + }, + async (params) => { + log("Tool invoked", "whatsapp.send-text", params); + const result = await fetchJSON("/send-text", { + method: "POST", + body: JSON.stringify(params), + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: whatsapp.send-image + server.tool( + "whatsapp.send-image", + "Sends an image message via WhatsApp.", + { + to: z.string().describe("Recipient ID"), + path: z.string().describe("Path or URL to the image"), + caption: z.string().optional().describe("Image caption"), + }, + async (params) => { + log("Tool invoked", "whatsapp.send-image", params); + const result = await fetchJSON("/send-image", { + method: "POST", + body: JSON.stringify(params), + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: whatsapp.send-file + server.tool( + "whatsapp.send-file", + "Sends a file message via WhatsApp.", + { + to: z.string().describe("Recipient ID"), + path: z.string().describe("Path or URL to the file"), + filename: z.string().optional().describe("Name of the file"), + caption: z.string().optional().describe("File caption"), + }, + async (params) => { + log("Tool invoked", "whatsapp.send-file", params); + const result = await fetchJSON("/send-file", { + method: "POST", + body: JSON.stringify(params), + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: whatsapp.list-chats + server.tool( + "whatsapp.list-chats", + "Retrieves a list of all chats.", + {}, // No input parameters + async () => { + log("Tool invoked", "whatsapp.list-chats"); + const result = await fetchJSON("/chats"); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: whatsapp.get-chat-details + server.tool( + "whatsapp.get-chat-details", + "Retrieves details for a specific chat by its ID.", + { + chatId: z.string().describe("ID of the chat (e.g., 1234567890@c.us)"), + }, + async (params) => { + log("Tool invoked", "whatsapp.get-chat-details", params); + const result = await fetchJSON(`/chats/${params.chatId}`); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: whatsapp.get-chat-messages + server.tool( + "whatsapp.get-chat-messages", + "Retrieves messages for a specific chat.", + { + chatId: z.string().describe("ID of the chat"), + limit: z.number().optional().describe("Number of messages to retrieve"), + includeMe: z.boolean().optional().describe("Include messages sent by me"), + includeNotifications: z.boolean().optional().describe("Include notification messages"), + }, + async ({ chatId, ...queryParams}) => { + log("Tool invoked", "whatsapp.get-chat-messages", { chatId, queryParams }); + const qs = new URLSearchParams( + Object.entries(queryParams) + .filter(([, v]) => v !== undefined) + .map(([k, v]) => [k, String(v)]) + ); + const result = await fetchJSON(`/chats/${chatId}/messages?${qs.toString()}`); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + server.tool( + "whatsapp.delete-chat", + "Deletes a chat by its ID. (Note: The underlying OpenWA endpoint for this is assumed and might need verification)", + { + chatId: z.string().describe("ID of the chat to delete (e.g., 1234567890@c.us)"), + }, + async (params) => { + log("Tool invoked", "whatsapp.delete-chat", params); + const result = await fetchJSON(`/chats/${params.chatId}`, { + method: "DELETE", + }); + // Consider what a successful deletion should return. + // OpenWA might return a success message or just a 200/204. + // For now, we'll return the full result. + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: whatsapp.create-group + server.tool( + "whatsapp.create-group", + "Creates a new WhatsApp group.", + { + groupName: z.string().describe("Name of the group"), + contactIds: z.array(z.string()).describe("Array of contact IDs to add to the group"), + }, + async (params) => { + log("Tool invoked", "whatsapp.create-group", params); + const result = await fetchJSON("/groups", { + method: "POST", + body: JSON.stringify(params), + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: whatsapp.get-contact-details + server.tool( + "whatsapp.get-contact-details", + "Retrieves details for a specific contact by ID.", + { + contactId: z.string().describe("ID of the contact (e.g., 1234567890@c.us)"), + }, + async (params) => { + log("Tool invoked", "whatsapp.get-contact-details", params); + const result = await fetchJSON(`/contacts/${params.contactId}`); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: whatsapp.get-blocklist + server.tool( + "whatsapp.get-blocklist", + "Retrieves the blocklist.", + {}, // No input parameters + async () => { + log("Tool invoked", "whatsapp.get-blocklist"); + const result = await fetchJSON("/blocklist"); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: whatsapp.block-contact + server.tool( + "whatsapp.block-contact", + "Blocks a contact on WhatsApp.", + { + contactId: z.string().describe("ID of the contact to block"), + }, + async (params) => { + log("Tool invoked", "whatsapp.block-contact", params); + const result = await fetchJSON("/blocklist/block", { + method: "POST", + body: JSON.stringify(params), + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + // Tool: whatsapp.unblock-contact + server.tool( + "whatsapp.unblock-contact", + "Unblocks a contact on WhatsApp.", + { + contactId: z.string().describe("ID of the contact to unblock"), + }, + async (params) => { + log("Tool invoked", "whatsapp.unblock-contact", params); + const result = await fetchJSON("/blocklist/unblock", { + method: "POST", + body: JSON.stringify(params), + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); +} diff --git a/whatsapp-router/src/routes/whatsappActions.ts b/whatsapp-router/src/routes/whatsappActions.ts index a288b3b..7c11487 100644 --- a/whatsapp-router/src/routes/whatsappActions.ts +++ b/whatsapp-router/src/routes/whatsappActions.ts @@ -48,6 +48,29 @@ router.post('/send-text', async (req: Request, res: Response) => { } }); +// DELETE /chats/:chatId +router.delete('/chats/:chatId', async (req: Request, res: Response) => { + try { + const { chatId } = req.params; + if (!chatId) { + return res.status(400).json({ error: 'Missing "chatId" in request params' }); + } + // openWaUrl is checked by middleware and available in module scope + const result = await whatsappClient.deleteChat(openWaUrl!, chatId); + res.json(result); + } catch (error: any) { + console.error(`[routes/whatsappActions] Error in DELETE /chats/${req.params.chatId}:`, error.message); + try { + // Attempt to parse error message if it's from whatsappClient's structured error + const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); + return res.status(parsedError.status || 500).json({ error: parsedError }); + } catch (e) { + // If not, send the plain error message + res.status(500).json({ error: error.message || 'Failed to delete chat' }); + } + } +}); + // POST /send-image router.post('/send-image', async (req: Request, res: Response) => { try { diff --git a/whatsapp-router/src/whatsappClient.ts b/whatsapp-router/src/whatsappClient.ts index ea3b369..b217e10 100644 --- a/whatsapp-router/src/whatsappClient.ts +++ b/whatsapp-router/src/whatsappClient.ts @@ -42,6 +42,34 @@ export async function sendTextMessage( } } +/** + * Deletes a chat by its ID via OpenWA. + * (Assumes OpenWA supports a /deleteChat endpoint or similar via POST) + * @param openWaUrl The base URL of the OpenWA instance. + * @param chatId The ID of the chat to delete. + * @returns A promise that resolves to the API response. + */ +export async function deleteChat( + openWaUrl: string, + chatId: string +): Promise { // Using OpenWAResponse for now + try { + // Assuming OpenWA uses a POST request for actions like deleteChat + // The actual endpoint name ('/deleteChat') is a guess and might need adjustment. + const response = await axios.post(`${openWaUrl}/deleteChat`, { + args: { chatId }, + }); + return response.data?.response || response.data; + } catch (error: any) { + console.error(`[whatsappClient] Error deleting chat ${chatId}:`, error.message); + if (axios.isAxiosError(error) && error.response) { + console.error('[whatsappClient] Axios error details:', error.response.data); + throw new Error(`whatsappClient API error (${openWaUrl}/deleteChat): ${error.response.status} - ${JSON.stringify(error.response.data)}`); + } + throw new Error(`whatsappClient error (${openWaUrl}/deleteChat): ${error.message}`); + } +} + /** * Sends an image message via OpenWA. * @param openWaUrl The base URL of the OpenWA instance. From acc7581169eeba6ae1212222958ae9a5cb885179 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Sat, 7 Jun 2025 00:21:55 -0600 Subject: [PATCH 2/2] =?UTF-8?q?cambios=20peque=C3=B1os=20para=20refinar=20?= =?UTF-8?q?el=20codigo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 5 +- conversation-layer-agent/src/index.ts | 15 +- mcp/index.js | 8 +- mcp/lib/api.js | 3 + mcp/package-lock.json | 337 +++++++++++++++++++++++++- mcp/package.json | 6 +- 6 files changed, 359 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 907c4ea..a36c91e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: sync-from-github sync-to-github chat router agent +.PHONY: sync-from-github sync-to-github chat router agent mcp # Pull latest changes from the GitHub mirror and push them to Gitea sync-from-github: @@ -18,4 +18,5 @@ router: agent: cd conversation-layer-agent && npm install && npm run dev - +mcp: + cd mcp && npm install && npm run dev \ No newline at end of file diff --git a/conversation-layer-agent/src/index.ts b/conversation-layer-agent/src/index.ts index 52f4631..70ec903 100644 --- a/conversation-layer-agent/src/index.ts +++ b/conversation-layer-agent/src/index.ts @@ -55,7 +55,7 @@ let mcpTransport: StreamableHTTPClientTransport | undefined; async function getMcpClient(): Promise { if (!mcpClient) { - mcpClient = new Client({ name: 'planilla-client', version: '1.0.0' }); + mcpClient = new Client({ name: 'conversation-layer-mcp-client', version: '1.0.0' }); mcpTransport = new StreamableHTTPClientTransport(new URL(MCP_URL)); await mcpClient.connect(mcpTransport); } @@ -73,7 +73,7 @@ const systemPromt = ` - cuando sos activado, tenes que entender los mensajes del objeto 'conversation' desde el ultimo mensaje pues es el mas nuevo. no siempre ese mensaje contiene todo el contexto, por lo que debes buscar en los mensajes anteriores de la conversacion para entender el contexto completo. - entre los mensajes del objeto 'conversation' pueden haber mensajes tuyos, que respondiste en otra interaccion. recorda que podes aprovechar esta capacidad para hablar con vos mismo en el futuro - - tenes la capacidad de llamar de manera secuencial a las herramientas del mcp planilla, eso significa que podes llamar una herramienta, esperar su respuesta y usar esa respuesta para llamar a la siguiente + - tenes la capacidad de llamar de manera secuencial a las herramientas del mcp que maneja la conversation-layer, eso significa que podes llamar una herramienta, esperar su respuesta y usar esa respuesta para llamar a la siguiente - la respuesta final siempre la tenes que dar hasta terminar de llamar a todas tus herramientas y obtener un resultado de cada una de ellas para luego usar eso para guiar tu respuesta final ## tu proposito @@ -95,6 +95,11 @@ const systemPromt = ` ###aclaratorias - los mensajes e2e son mensajes que utiliza whatsapp para notificar cosas de su sistema, no son mensajes de los usuarios y no debes responderlos. - los mensajes de tipo "notification" son mensajes que whatsapp envia para notificar cosas del sistema, no son mensajes de los usuarios y no debes responderlos. + +### mcp + aqui se encuentrar registradas todas las herramientas que podes utilizar, hay dos grandes grupos de herramientas: conversation y whatsapp. + - las herramientas de conversation son para manipular los conversation objects que al final es todo lo que todos los agents ven + - las herramientas de whatsapp son para manipular las acciones que nucleo toma en su propiia cuenta de whatsapp, por ejemplo enviar mensajes, crear grupos, etc. `; const app = express(); @@ -110,14 +115,14 @@ app.post('/', async (req, res) => { try { const contents = `systemPrompt: ${systemPromt}\nConversation:\n${JSON.stringify(conversation)}\n`; - console.log(' contents', contents); + // console.log(' contents', contents); const config: any = {}; // if (message.toLowerCase().includes('/planilla')) { if (true) { console.log('Using Model Context Protocol tools ', MCP_URL); - // const client = await getMcpClient(); - // config.tools = [mcpToTool(client)]; + const client = await getMcpClient(); + config.tools = [mcpToTool(client)]; } const result = await genAI.models.generateContent({ model: 'gemini-2.0-flash', diff --git a/mcp/index.js b/mcp/index.js index 67de9e3..a11744c 100644 --- a/mcp/index.js +++ b/mcp/index.js @@ -3,7 +3,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/ import express from "express"; import { createServer } from "./createServer.js"; -console.log('este no tiene variables de entorno, es un servidor MCP Planilla'); +console.log('este no tiene variables de entorno, es un servidor MCP conversation-layer'); async function main() { @@ -13,14 +13,14 @@ async function main() { const server = createServer(); const transport = new StdioServerTransport(); await server.connect(transport); - console.log("MCP Planilla server listo (stdio)"); + console.log("MCP conversation-layer server listo (stdio)"); } else { // bootLog("Modo transporte: HTTP streamable"); const app = express(); app.use(express.json()); app.use((req, _res, next) => { - console.log(`[HTTP] ${req.method} ${req.originalUrl} ${req.statusCode} ${req.res.body}`); + console.log(`[HTTP] ${req.method} ${req.originalUrl} ${req.statusCode}`); next(); }); @@ -59,7 +59,7 @@ async function main() { ); app.listen(port, () => { - console.log(`MCP Planilla HTTP server listening on port ${port}`); + console.log(`MCP conversation-layer HTTP server listening on port ${port}`); }); } } diff --git a/mcp/lib/api.js b/mcp/lib/api.js index 2595cfd..4e83684 100644 --- a/mcp/lib/api.js +++ b/mcp/lib/api.js @@ -1,3 +1,6 @@ +import dotenv from "dotenv"; +dotenv.config(); + export const API_BASE_URL = process.env.WHATSAPP_ROUTER_URL; if (!API_BASE_URL) { diff --git a/mcp/package-lock.json b/mcp/package-lock.json index 968c747..19e43d6 100644 --- a/mcp/package-lock.json +++ b/mcp/package-lock.json @@ -1,19 +1,21 @@ { - "name": "planilla-mcp-server", + "name": "conversation-layer-mcp-server", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "planilla-mcp-server", + "name": "conversation-layer-mcp-server", "version": "0.1.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", + "dotenv": "^16.5.0", "express": "^5.1.0", "zod": "^3.24.3" }, "devDependencies": { "node-cron": "^4.0.5", + "nodemon": "^3.1.10", "prisma": "^6.8.2" } }, @@ -104,6 +106,37 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -124,6 +157,28 @@ "node": ">=18" } }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -161,6 +216,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -248,6 +333,17 @@ "node": ">= 0.8" } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -393,6 +489,18 @@ "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", @@ -427,6 +535,20 @@ "node": ">= 0.8" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -473,6 +595,18 @@ "node": ">= 0.4" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -485,6 +619,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -536,6 +679,12 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -549,6 +698,48 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -619,6 +810,18 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -643,6 +846,43 @@ "node": ">=6.0.0" } }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -698,6 +938,18 @@ "node": ">=8" } }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkce-challenge": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", @@ -743,6 +995,12 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -780,6 +1038,18 @@ "node": ">= 0.8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -828,6 +1098,18 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", @@ -961,6 +1243,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -969,6 +1263,30 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -977,6 +1295,15 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -991,6 +1318,12 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/mcp/package.json b/mcp/package.json index e8b6f2f..529db0e 100644 --- a/mcp/package.json +++ b/mcp/package.json @@ -1,5 +1,5 @@ { - "name": "planilla-mcp-server", + "name": "conversation-layer-mcp-server", "version": "0.1.0", "type": "module", "scripts": { @@ -8,11 +8,13 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", + "dotenv": "^16.5.0", "express": "^5.1.0", "zod": "^3.24.3" }, - "devDependencies": { + "devDependencies": { "node-cron": "^4.0.5", + "nodemon": "^3.1.10", "prisma": "^6.8.2" } }