Initial commit

This commit is contained in:
Jordan
2026-04-01 23:16:45 +01:00
commit bc4199aed2
201 changed files with 25612 additions and 0 deletions

224
mcp-server/monitor.js Normal file
View File

@@ -0,0 +1,224 @@
import http from "node:http";
import fsPromises from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { MONITOR_PORT, MONITOR_DISABLED } from "./config/index.js";
import { sessionCredentials } from "./auth/credentials.js";
import { activeSessions } from "./httpServer.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const monitorHtmlPath = path.join(__dirname, "monitor.html");
/**
* Get active sessions with their credentials info
*/
function getActiveSessions() {
const sessions = [];
for (const [sessionId, sessionData] of activeSessions.entries()) {
const creds = sessionCredentials.get(sessionId);
sessions.push({
sessionId,
website: creds?.website || null,
hasToken: !!creds?.token,
hasTokenHash: !!creds?.tokenHash,
profileName: creds?.profileName || null,
startTime: sessionData.startTime,
durationMs: Date.now() - sessionData.startTime
});
}
return sessions.sort((a, b) => b.startTime - a.startTime);
}
// SSE clients for real-time updates
export const sseClients = new Set();
/**
* Get the monitor HTML page
*/
async function getMonitorHtml() {
try {
return await fsPromises.readFile(monitorHtmlPath, "utf-8");
} catch (error) {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>MCP Monitor</title>
<style>
body { font-family: sans-serif; background: #111827; color: #e5e7eb; margin: 0; padding: 2rem; }
</style>
</head>
<body>
<h1>MCP Monitor</h1>
<p>No se encontró el archivo de interfaz en <code>${monitorHtmlPath}</code>.</p>
</body>
</html>`;
}
}
/**
* Broadcast an SSE event to all connected clients
*/
export function broadcastSse(event, payload) {
const chunk = `event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`;
for (const client of sseClients) {
try {
client.write(chunk);
} catch {
sseClients.delete(client);
}
}
}
/**
* Broadcast sessions update to all connected monitor clients
*/
export function broadcastSessionsUpdate() {
broadcastSse("sessions", { sessions: getActiveSessions() });
}
/**
* Start the monitor HTTP server
*/
export function startMonitorServer(requestMonitor, toolHandlers) {
if (MONITOR_DISABLED) {
console.error("MCP monitor UI deshabilitada (MCP_MONITOR_DISABLED=1).");
return null;
}
const monitorServer = http.createServer(async (req, res) => {
const url = new URL(req.url ?? "/", "http://localhost");
if (req.method === "GET" && (url.pathname === "/" || url.pathname === "/monitor")) {
const html = await getMonitorHtml();
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.end(html);
return;
}
if (req.method === "GET" && url.pathname === "/requests") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ requests: requestMonitor.getSummaries() }));
return;
}
if (req.method === "GET" && url.pathname.startsWith("/requests/")) {
const [, , rawId] = url.pathname.split("/");
const entry = requestMonitor.getEntryById(rawId);
if (!entry) {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Request not found" }));
return;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(entry));
return;
}
if (req.method === "GET" && url.pathname === "/sessions") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ sessions: getActiveSessions() }));
return;
}
if (req.method === "GET" && url.pathname === "/stats") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ stats: requestMonitor.getSessionStats() }));
return;
}
if (req.method === "GET" && url.pathname === "/events") {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
});
res.write("event: bootstrap\n");
res.write(`data: ${JSON.stringify({
requests: requestMonitor.getSummaries(),
sessions: getActiveSessions(),
stats: requestMonitor.getSessionStats()
})}\n\n`);
sseClients.add(res);
req.on("close", () => {
sseClients.delete(res);
});
return;
}
if (req.method === "POST" && url.pathname.startsWith("/retry/")) {
const [, , rawId] = url.pathname.split("/");
const entry = requestMonitor.getEntryById(rawId);
if (!entry) {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Request not found" }));
return;
}
if (entry.method !== "tools/call") {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Only tool calls can be retried" }));
return;
}
const toolName = entry.request.params.name;
const toolHandler = toolHandlers.get(toolName);
if (!toolHandler) {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: `Tool handler for '${toolName}' not found` }));
return;
}
try {
const extra = { sessionId: entry.sessionId };
const retryEntry = requestMonitor.start(entry.method, entry.request, extra);
const result = await toolHandler.handler(entry.request.params.arguments, extra);
requestMonitor.finish(retryEntry, result);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ success: true, newRequestId: retryEntry.id, result }));
} catch (error) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: error.message }));
}
return;
}
if (req.method === "GET" && url.pathname === "/health") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok" }));
return;
}
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Not found" }));
});
monitorServer.on("error", (error) => {
console.warn(
`[monitor] No se pudo iniciar la UI en el puerto ${MONITOR_PORT}: ${error.message}. Establece MCP_MONITOR_DISABLED=1 para ocultar este aviso.`
);
});
monitorServer.listen(MONITOR_PORT, '0.0.0.0', () => {
console.error(`MCP monitor UI: http://0.0.0.0:${MONITOR_PORT}/monitor`);
});
// Broadcast sessions + stats update every 2 seconds for real-time monitoring
setInterval(() => {
if (sseClients.size > 0) {
broadcastSessionsUpdate();
broadcastSse("stats", { stats: requestMonitor.getSessionStats() });
}
}, 2000);
return monitorServer;
}