iniciada creacion del mcp
All checks were successful
Deploy conversation layer / deploy (push) Successful in 6s

This commit is contained in:
2025-06-06 20:51:06 -06:00
parent b39db9ad06
commit 7eba5b2755
9 changed files with 1316 additions and 0 deletions

25
mcp/.gitignore vendored Normal file
View File

@@ -0,0 +1,25 @@
# Dependencies
node_modules/
# Environment variables
.env
.env.*
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Build output
dist/
coverage/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea/
.DS_Store

7
mcp/Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5000
CMD ["node", "index.js"]

13
mcp/README.md Normal file
View File

@@ -0,0 +1,13 @@
# Planilla MCP Server
This module exposes the planilla API through the Model Context Protocol (MCP).
By default it runs an HTTP server using the Streamable HTTP transport. You can
switch to STDIO communication by passing `--stdio` when starting.
## Usage
```bash
npm install
npm start # HTTP mode on PORT (default 5000)
npm start -- --stdio # STDIO mode
```

11
mcp/createServer.js Normal file
View File

@@ -0,0 +1,11 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import registerTareas from "./modules/tareas.js";
export function createServer() {
const server = new McpServer({ name: "planilla-mcp", version: "0.1.0" });
registerTareas(server);
return server;
}

70
mcp/index.js Normal file
View File

@@ -0,0 +1,70 @@
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
import { createServer } from "./createServer.js";
console.log('este no tiene variables de entorno, es un servidor MCP Planilla');
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());
app.use((req, _res, next) => {
console.log(`[HTTP] ${req.method} ${req.originalUrl} ${req.statusCode} ${req.res.body}`);
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);
});

15
mcp/lib/api.js Normal file
View File

@@ -0,0 +1,15 @@
export const API_BASE_URL = process.env.PLANILLA_API_URL || "http://localhost:4000";
export 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();
}

111
mcp/modules/tareas.js Normal file
View File

@@ -0,0 +1,111 @@
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) }] };
}
);
}

1046
mcp/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
mcp/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "planilla-mcp-server",
"version": "0.1.0",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon src/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"express": "^5.1.0",
"zod": "^3.24.3"
},
"devDependencies": {
"node-cron": "^4.0.5",
"prisma": "^6.8.2"
}
}