Files
planilla/mcp/index.js
josedario87 c7d4dbeea6
All checks were successful
build-and-deploy / filter (push) Successful in 2s
build-and-deploy / deploy (push) Successful in 31s
build-and-deploy / build (push) Successful in 6s
agregado datos de log para el MCP
2025-06-03 17:04:15 -06:00

224 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 🚨 Nuevos logs en puntos clave para saber qué se pide y cuándo
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
import { z } from "zod";
// -------------------------------
// 🔧 VARIABLES Y HELPERS GLOBALES
// -------------------------------
const API_BASE_URL = process.env.PLANILLA_API_URL || "http://localhost:4000";
const log = (...args) => console.log("[MCP]", ...args);
const bootLog = (...args) => console.log("[BOOT]", ...args);
// Imprime detalles del runtime apenas carga el módulo
(function printBootInfo () {
bootLog("Iniciando Planilla MCP v0.1.0");
bootLog(`Node : ${process.version} (${process.platform}-${process.arch})`);
bootLog(`Working directory : ${process.cwd()}`);
bootLog(`PLANILLA_API_URL : ${API_BASE_URL}`);
bootLog(`PORT (HTTP) : ${process.env.PORT ?? "5000 (default)"}`);
bootLog(`Args : ${process.argv.slice(2).join(" ") || "<none>"}`);
const rss = (process.memoryUsage().rss / 1024 ** 2).toFixed(1);
bootLog(`Boot RSS Memory : ${rss}MB`);
})();
// --------------------------------
// ⛑️ FETCH HELPER CON LOGGING
// --------------------------------
async function fetchJSON (path, options = {}) {
const method = options.method || "GET";
console.log(`[API] ${method} ${API_BASE_URL}${path}`);
const res = await fetch(`${API_BASE_URL}${path}`, {
headers: { "Content-Type": "application/json" },
...options,
});
if (!res.ok) {
const txt = await res.text();
throw new Error(`API request failed (${res.status}): ${txt}`);
}
return res.json();
}
// -------------------------------
// 🏗️ CREACIÓN DEL MCP SERVER
// -------------------------------
function createServer () {
const server = new McpServer({ name: "planilla-mcp", version: "0.1.0" });
log("Servidor MCP instanciado");
// ----- Resources -----
server.resource("planilla-list", "planilla://list", async (uri) => {
log("Recurso solicitado", "planilla-list");
const planillas = await fetchJSON("/api/planillas");
return { contents: [{ uri: uri.href, text: JSON.stringify(planillas) }] };
});
server.resource(
"planilla",
new ResourceTemplate("planilla://{id}", { list: undefined }),
async (uri, { id }) => {
log("Recurso solicitado", `planilla ${id}`);
const planilla = await fetchJSON(`/api/planillas/${id}`);
return { contents: [{ uri: uri.href, text: JSON.stringify(planilla) }] };
}
);
// ----- Tools -----
server.tool(
"create-planilla",
"Crea una planilla",
{
empleado_id: z.number(),
fecha_desde: z.string(),
fecha_hasta: z.string(),
titulo: z.string(),
total: z.number().optional(),
estado: z.string().optional(),
fecha_anulado:z.string().optional(),
creador_id: z.number().optional(),
anulador_id: z.number().optional(),
},
async (params) => {
log("Tool invocada", "create-planilla", params);
const planilla = await fetchJSON("/api/planillas", {
method: "POST",
body: JSON.stringify(params),
});
return { content: [{ type: "text", text: JSON.stringify(planilla) }] };
}
);
server.tool(
"update-planilla",
"Actualiza una planilla existente",
{
id: z.number(),
empleado_id: z.number().optional(),
fecha_desde: z.string().optional(),
fecha_hasta: z.string().optional(),
titulo: z.string().optional(),
total: z.number().optional(),
estado: z.string().optional(),
fecha_anulado:z.string().optional(),
anulador_id: z.number().optional(),
},
async ({ id, ...updates }) => {
log("Tool invocada", "update-planilla", { id, ...updates });
const planilla = await fetchJSON(`/api/planillas/${id}`, {
method: "PUT",
body: JSON.stringify(updates),
});
return { content: [{ type: "text", text: JSON.stringify(planilla) }] };
}
);
server.tool(
"delete-planilla",
"Elimina una planilla",
{ id: z.number() },
async ({ id }) => {
log("Tool invocada", "delete-planilla", { id });
await fetchJSON(`/api/planillas/${id}`, { method: "DELETE" });
return { content: [{ type: "text", text: `Planilla ${id} eliminada` }] };
}
);
server.tool(
"search-planillas",
"Busca planillas. `q` es un texto libre que matchea id, empleado_id, título o estado. Si no mandás ningún argumento devuelve los primeros 100 registros.",
{
q: z.string().optional(),
empleado_id: z.number().optional(),
estado: z.string().optional(),
titulo: z.string().optional(),
fecha_desde_desde: z.string().optional(),
fecha_desde_hasta: z.string().optional(),
fecha_hasta_desde: z.string().optional(),
fecha_hasta_hasta: z.string().optional(),
},
async (params) => {
log("Tool invocada", "search-planillas", params);
const qs = new URLSearchParams(
Object.entries(params).filter(([, v]) => v !== undefined).map(([k, v]) => [k, String(v)])
);
// 👇 Si no hay filtros, limit=100
if (qs.toString() === "") qs.append("limit", "100");
const planillas = await fetchJSON(`/api/planillas/search?${qs.toString()}`);
return { content: [{ type: "text", text: JSON.stringify(planillas) }] };
}
);
return server;
}
// --------------------------------
// 🚀 PUNTO DE ENTRADA
// --------------------------------
async function main () {
const useStdio = process.argv.includes("--stdio");
if (useStdio) {
bootLog("Modo transporte: stdio");
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
console.log("MCP Planilla server listo (stdio)");
} else {
bootLog("Modo transporte: HTTP streamable");
const app = express();
app.use(express.json());
// 🌐 Log de cada request HTTP entrante
app.use((req, _res, next) => {
console.log(`[HTTP] ${req.method} ${req.originalUrl}`);
next();
});
const port = process.env.PORT || 5000;
app.post("/mcp", async (req, res) => {
try {
const server = createServer();
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
res.on("close", () => {
transport.close();
server.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
["get", "delete"].forEach((m) =>
app[m]("/mcp", (_req, res) =>
res.status(405).json({
jsonrpc: "2.0",
error: { code: -32000, message: "Method not allowed." },
id: null,
})
)
);
app.listen(port, () => {
console.log(`MCP Planilla HTTP server listening on port ${port}`);
});
}
}
main().catch((err) => {
console.error("Error fatal en MCP server:", err);
process.exit(1);
});