[DEV] Tool _webmcp_server-info: estado del servidor MCP
Muestra version, commit git, host, puerto, canales activos, clientes conectados, registry (tools/prompts/resources), uptime y requests pendientes. Solo modo dev.
This commit is contained in:
@@ -10784,7 +10784,7 @@ var wrapper_default = import_websocket.default;
|
|||||||
// src/websocket-server.js
|
// src/websocket-server.js
|
||||||
import { createServer } from "http";
|
import { createServer } from "http";
|
||||||
import { parse as parse3 } from "url";
|
import { parse as parse3 } from "url";
|
||||||
import { fork } from "child_process";
|
import { fork, execSync as execSync2 } from "child_process";
|
||||||
|
|
||||||
// node_modules/zod/v3/helpers/util.js
|
// node_modules/zod/v3/helpers/util.js
|
||||||
var util;
|
var util;
|
||||||
@@ -25197,6 +25197,31 @@ ${token}`
|
|||||||
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (request.params.name === "_webmcp_server-info") {
|
||||||
|
if (!CONFIG.dev) {
|
||||||
|
return { content: [{ type: "text", text: "server-info solo esta disponible en modo desarrollo (--dev)" }], isError: true };
|
||||||
|
}
|
||||||
|
if (!wsClient || wsClient.readyState !== wrapper_default.OPEN) {
|
||||||
|
return { content: [{ type: "text", text: "No hay conexion al servidor WebSocket" }], isError: true };
|
||||||
|
}
|
||||||
|
const requestId2 = (requestIdCounter++).toString();
|
||||||
|
const responsePromise2 = new Promise((resolve, reject) => {
|
||||||
|
pendingRequests.set(requestId2, { resolve, reject });
|
||||||
|
setTimeout(() => {
|
||||||
|
if (pendingRequests.has(requestId2)) {
|
||||||
|
pendingRequests.delete(requestId2);
|
||||||
|
reject(new Error("Timeout obteniendo info del servidor"));
|
||||||
|
}
|
||||||
|
}, 1e4);
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await sendMessage({ id: requestId2, type: "getServerInfo" });
|
||||||
|
const result = await responsePromise2;
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!wsClient || wsClient.readyState !== wrapper_default.OPEN) {
|
if (!wsClient || wsClient.readyState !== wrapper_default.OPEN) {
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
@@ -25288,6 +25313,14 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
todas: { type: "boolean", description: "Si es true, quita todas las herramientas" }
|
todas: { type: "boolean", description: "Si es true, quita todas las herramientas" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_webmcp_server-info",
|
||||||
|
description: "[DEV] Muestra estado del servidor @nucleoriofrio/webmcp: version, commit, host, puerto, canales activos, clientes, registry de tools/prompts/resources, uptime y requests pendientes.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -25548,6 +25581,12 @@ function sendNotification(clientWs, channelPath, notificationType, data, mcpOnly
|
|||||||
var toolsRegistry = {};
|
var toolsRegistry = {};
|
||||||
var promptsRegistry = {};
|
var promptsRegistry = {};
|
||||||
var resourcesRegistry = {};
|
var resourcesRegistry = {};
|
||||||
|
var serverStartTime = Date.now();
|
||||||
|
var gitCommit = "unknown";
|
||||||
|
try {
|
||||||
|
gitCommit = execSync2("git rev-parse --short HEAD", { stdio: ["pipe", "pipe", "ignore"] }).toString().trim();
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
var requestIdCounter2 = 1;
|
var requestIdCounter2 = 1;
|
||||||
var pendingRequests2 = {};
|
var pendingRequests2 = {};
|
||||||
async function verifyClientToken(info, callback) {
|
async function verifyClientToken(info, callback) {
|
||||||
@@ -25738,6 +25777,9 @@ wss.on("connection", (ws, req) => {
|
|||||||
case "getClientInfo":
|
case "getClientInfo":
|
||||||
handleGetClientInfo(ws, clientChannel, data);
|
handleGetClientInfo(ws, clientChannel, data);
|
||||||
break;
|
break;
|
||||||
|
case "getServerInfo":
|
||||||
|
handleGetServerInfo(ws, data);
|
||||||
|
break;
|
||||||
case "clearRegistry":
|
case "clearRegistry":
|
||||||
handleClearRegistry(ws, data);
|
handleClearRegistry(ws, data);
|
||||||
break;
|
break;
|
||||||
@@ -26123,6 +26165,45 @@ function handleGetClientInfo(ws, callerChannel, data) {
|
|||||||
result: { content: [{ type: "text", text }] }
|
result: { content: [{ type: "text", text }] }
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
function handleGetServerInfo(ws, data) {
|
||||||
|
const { id } = data;
|
||||||
|
const uptimeMs = Date.now() - serverStartTime;
|
||||||
|
const uptimeSec = Math.floor(uptimeMs / 1e3);
|
||||||
|
const hours = Math.floor(uptimeSec / 3600);
|
||||||
|
const minutes = Math.floor(uptimeSec % 3600 / 60);
|
||||||
|
const seconds = uptimeSec % 60;
|
||||||
|
const channelsList = {};
|
||||||
|
for (const [channelPath, channelClients] of Object.entries(channels)) {
|
||||||
|
channelsList[channelPath] = channelClients.size;
|
||||||
|
}
|
||||||
|
const info = {
|
||||||
|
server: `@nucleoriofrio/webmcp v0.2.0`,
|
||||||
|
commit: gitCommit,
|
||||||
|
host: HOST,
|
||||||
|
port: CONFIG.port,
|
||||||
|
dev: !!CONFIG.dev,
|
||||||
|
uptime: `${hours}h ${minutes}m ${seconds}s`,
|
||||||
|
uptimeMs,
|
||||||
|
channels: channelsList,
|
||||||
|
totalClients: Object.values(channels).reduce((sum, ch) => sum + ch.size, 0),
|
||||||
|
registry: {
|
||||||
|
tools: Object.keys(toolsRegistry).length,
|
||||||
|
prompts: Object.keys(promptsRegistry).length,
|
||||||
|
resources: Object.keys(resourcesRegistry).length
|
||||||
|
},
|
||||||
|
toolsList: Object.entries(toolsRegistry).map(([id2, info2]) => ({
|
||||||
|
id: id2,
|
||||||
|
name: info2.originalName,
|
||||||
|
channel: info2.channel
|
||||||
|
})),
|
||||||
|
pendingRequests: Object.keys(pendingRequests2).length
|
||||||
|
};
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
id,
|
||||||
|
type: "toolResponse",
|
||||||
|
result: { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] }
|
||||||
|
}));
|
||||||
|
}
|
||||||
function handleClearRegistry(ws, data) {
|
function handleClearRegistry(ws, data) {
|
||||||
const { id } = data;
|
const { id } = data;
|
||||||
let toolCount = Object.keys(toolsRegistry).length;
|
let toolCount = Object.keys(toolsRegistry).length;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -407,6 +407,34 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.params.name === "_webmcp_server-info") {
|
||||||
|
if (!CONFIG.dev) {
|
||||||
|
return { content: [{ type: "text", text: "server-info solo esta disponible en modo desarrollo (--dev)" }], isError: true };
|
||||||
|
}
|
||||||
|
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
|
||||||
|
return { content: [{ type: "text", text: "No hay conexion al servidor WebSocket" }], isError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestId = (requestIdCounter++).toString();
|
||||||
|
const responsePromise = new Promise((resolve, reject) => {
|
||||||
|
pendingRequests.set(requestId, { resolve, reject });
|
||||||
|
setTimeout(() => {
|
||||||
|
if (pendingRequests.has(requestId)) {
|
||||||
|
pendingRequests.delete(requestId);
|
||||||
|
reject(new Error('Timeout obteniendo info del servidor'));
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendMessage({ id: requestId, type: 'getServerInfo' });
|
||||||
|
const result = await responsePromise;
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
|
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
@@ -513,6 +541,14 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
todas: { type: "boolean", description: "Si es true, quita todas las herramientas" }
|
todas: { type: "boolean", description: "Si es true, quita todas las herramientas" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_webmcp_server-info",
|
||||||
|
description: "[DEV] Muestra estado del servidor @nucleoriofrio/webmcp: version, commit, host, puerto, canales activos, clientes, registry de tools/prompts/resources, uptime y requests pendientes.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as fs from 'fs/promises';
|
|||||||
import {WebSocketServer, WebSocket} from 'ws';
|
import {WebSocketServer, WebSocket} from 'ws';
|
||||||
import {createServer} from 'http';
|
import {createServer} from 'http';
|
||||||
import {parse} from 'url';
|
import {parse} from 'url';
|
||||||
import {fork} from 'child_process';
|
import {fork, execSync} from 'child_process';
|
||||||
import {runMcpServer} from './server.js';
|
import {runMcpServer} from './server.js';
|
||||||
import {
|
import {
|
||||||
clearTokens,
|
clearTokens,
|
||||||
@@ -97,6 +97,17 @@ const toolsRegistry = {};
|
|||||||
const promptsRegistry = {};
|
const promptsRegistry = {};
|
||||||
const resourcesRegistry = {};
|
const resourcesRegistry = {};
|
||||||
|
|
||||||
|
// Server start timestamp for uptime calculation
|
||||||
|
const serverStartTime = Date.now();
|
||||||
|
|
||||||
|
// Git commit hash (read once at startup)
|
||||||
|
let gitCommit = 'unknown';
|
||||||
|
try {
|
||||||
|
gitCommit = execSync('git rev-parse --short HEAD', { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim();
|
||||||
|
} catch (e) {
|
||||||
|
// Not in a git repo or git not available
|
||||||
|
}
|
||||||
|
|
||||||
// Request counter for unique IDs
|
// Request counter for unique IDs
|
||||||
let requestIdCounter = 1;
|
let requestIdCounter = 1;
|
||||||
|
|
||||||
@@ -371,6 +382,10 @@ wss.on('connection', (ws, req) => {
|
|||||||
handleGetClientInfo(ws, clientChannel, data);
|
handleGetClientInfo(ws, clientChannel, data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'getServerInfo':
|
||||||
|
handleGetServerInfo(ws, data);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'clearRegistry':
|
case 'clearRegistry':
|
||||||
handleClearRegistry(ws, data);
|
handleClearRegistry(ws, data);
|
||||||
break;
|
break;
|
||||||
@@ -875,6 +890,51 @@ function handleGetClientInfo(ws, callerChannel, data) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle getServerInfo request - return server status and stats
|
||||||
|
function handleGetServerInfo(ws, data) {
|
||||||
|
const { id } = data;
|
||||||
|
|
||||||
|
const uptimeMs = Date.now() - serverStartTime;
|
||||||
|
const uptimeSec = Math.floor(uptimeMs / 1000);
|
||||||
|
const hours = Math.floor(uptimeSec / 3600);
|
||||||
|
const minutes = Math.floor((uptimeSec % 3600) / 60);
|
||||||
|
const seconds = uptimeSec % 60;
|
||||||
|
|
||||||
|
const channelsList = {};
|
||||||
|
for (const [channelPath, channelClients] of Object.entries(channels)) {
|
||||||
|
channelsList[channelPath] = channelClients.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = {
|
||||||
|
server: `@nucleoriofrio/webmcp v0.2.0`,
|
||||||
|
commit: gitCommit,
|
||||||
|
host: HOST,
|
||||||
|
port: CONFIG.port,
|
||||||
|
dev: !!CONFIG.dev,
|
||||||
|
uptime: `${hours}h ${minutes}m ${seconds}s`,
|
||||||
|
uptimeMs,
|
||||||
|
channels: channelsList,
|
||||||
|
totalClients: Object.values(channels).reduce((sum, ch) => sum + ch.size, 0),
|
||||||
|
registry: {
|
||||||
|
tools: Object.keys(toolsRegistry).length,
|
||||||
|
prompts: Object.keys(promptsRegistry).length,
|
||||||
|
resources: Object.keys(resourcesRegistry).length,
|
||||||
|
},
|
||||||
|
toolsList: Object.entries(toolsRegistry).map(([id, info]) => ({
|
||||||
|
id,
|
||||||
|
name: info.originalName,
|
||||||
|
channel: info.channel,
|
||||||
|
})),
|
||||||
|
pendingRequests: Object.keys(pendingRequests).length,
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
id,
|
||||||
|
type: 'toolResponse',
|
||||||
|
result: { content: [{ type: 'text', text: JSON.stringify(info, null, 2) }] }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// Handle clearing all registries
|
// Handle clearing all registries
|
||||||
function handleClearRegistry(ws, data) {
|
function handleClearRegistry(ws, data) {
|
||||||
const {id} = data;
|
const {id} = data;
|
||||||
|
|||||||
Reference in New Issue
Block a user