Subir archivos a "/"

This commit is contained in:
2026-03-06 02:41:54 -06:00
parent 59284c2876
commit 0244f0f5a4
5 changed files with 3529 additions and 0 deletions

889
server.js Normal file
View File

@@ -0,0 +1,889 @@
import {Server} from "@modelcontextprotocol/sdk/server/index.js";
import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
import WebSocket from 'ws';
import {
CallToolRequestSchema,
CreateMessageRequestSchema,
GetPromptRequestSchema,
ListPromptsRequestSchema,
ListResourcesRequestSchema,
ListResourceTemplatesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import {generateNewRegistrationToken} from "./tokens.js";
import {CONFIG} from "./config.js";
import {execSync} from "child_process";
// Create a central MCP server that communicates over stdio
const mcpServer = new Server(
{
name: "@nucleoriofrio/webmcp",
version: "0.2.0",
},
{
capabilities: {
tools: {
listChanged: true
},
prompts: {
listChanged: true
},
resources: {
listChanged: true,
subscribe: true
},
sampling: {}
}
}
);
// WebSocket client connection
let wsClient = null;
// MCP specific channel path
const MCP_PATH = '/mcp';
// Map to store pending requests from WebSocket to MCP
const pendingRequests = new Map();
let requestIdCounter = 1;
// Debounce timers for list changed notifications
let toolListChangedTimer = null;
let promptListChangedTimer = null;
let resourceListChangedTimer = null;
const LIST_CHANGED_DEBOUNCE_MS = 500;
function debouncedToolListChanged() {
if (toolListChangedTimer) clearTimeout(toolListChangedTimer);
toolListChangedTimer = setTimeout(async () => {
toolListChangedTimer = null;
try { await mcpServer.sendToolListChanged(); } catch (e) { console.error('Error sending tool list changed:', e); }
}, LIST_CHANGED_DEBOUNCE_MS);
}
function debouncedPromptListChanged() {
if (promptListChangedTimer) clearTimeout(promptListChangedTimer);
promptListChangedTimer = setTimeout(async () => {
promptListChangedTimer = null;
try { await mcpServer.sendPromptListChanged(); } catch (e) { console.error('Error sending prompt list changed:', e); }
}, LIST_CHANGED_DEBOUNCE_MS);
}
function debouncedResourceListChanged() {
if (resourceListChangedTimer) clearTimeout(resourceListChangedTimer);
resourceListChangedTimer = setTimeout(async () => {
resourceListChangedTimer = null;
try { await mcpServer.sendResourceListChanged(); } catch (e) { console.error('Error sending resource list changed:', e); }
}, LIST_CHANGED_DEBOUNCE_MS);
}
// Function to handle WebSocket messages
async function handleWebSocketMessage(message) {
try {
const data = JSON.parse(message);
console.error(`Received message: ${data.type}`);
if (data.type === 'toolResponse') {
// Handle tool response from WebSocket server
const {id, result, error} = data;
// Check if this is a response to a pending request
if (pendingRequests.has(id)) {
const {resolve, reject} = pendingRequests.get(id);
pendingRequests.delete(id);
if (error) {
reject(new Error(error));
} else {
resolve(result);
}
} else {
console.error(`No pending request found for ID: ${id}`);
}
} else if (data.type === 'promptResponse') {
// Handle prompt response from WebSocket server
const {id, result, error} = data;
// Check if this is a response to a pending request
if (pendingRequests.has(id)) {
const {resolve, reject} = pendingRequests.get(id);
pendingRequests.delete(id);
if (error) {
reject(new Error(error));
} else {
resolve(result);
}
} else {
console.error(`No pending request found for ID: ${id}`);
}
} else if (data.type === 'resourceResponse') {
// Handle resource response from WebSocket server
const {id, result, error} = data;
// Check if this is a response to a pending request
if (pendingRequests.has(id)) {
const {resolve, reject} = pendingRequests.get(id);
pendingRequests.delete(id);
if (error) {
reject(new Error(error));
} else {
resolve(result);
}
} else {
console.error(`No pending request found for ID: ${id}`);
}
} else if (data.type === 'samplingResponse') {
// Handle sampling response from WebSocket server
const {id, result, error} = data;
// Check if this is a response to a pending request
if (pendingRequests.has(id)) {
const {resolve, reject} = pendingRequests.get(id);
pendingRequests.delete(id);
if (error) {
reject(new Error(error));
} else {
resolve(result);
}
} else {
console.error(`No pending request found for ID: ${id}`);
}
} else if (data.type === 'listToolsResponse') {
// Handle list tools response from WebSocket server
const {id, tools, error} = data;
// Check if this is a response to a pending request
if (pendingRequests.has(id)) {
const {resolve, reject} = pendingRequests.get(id);
pendingRequests.delete(id);
if (error) {
reject(new Error(error));
} else {
resolve(tools);
}
} else {
console.error(`No pending request found for ID: ${id}`);
}
} else if (data.type === 'listPromptsResponse') {
// Handle list prompts response from WebSocket server
const {id, prompts, error} = data;
// Check if this is a response to a pending request
if (pendingRequests.has(id)) {
const {resolve, reject} = pendingRequests.get(id);
pendingRequests.delete(id);
if (error) {
reject(new Error(error));
} else {
resolve(prompts);
}
} else {
console.error(`No pending request found for ID: ${id}`);
}
} else if (data.type === 'listResourcesResponse') {
// Handle list resources response from WebSocket server
const {id, resources, resourceTemplates, error} = data;
// Check if this is a response to a pending request
if (pendingRequests.has(id)) {
const {resolve, reject} = pendingRequests.get(id);
pendingRequests.delete(id);
if (error) {
reject(new Error(error));
} else {
resolve({resources, resourceTemplates});
}
} else {
console.error(`No pending request found for ID: ${id}`);
}
} else if (data.type === 'toolRegistered') {
debouncedToolListChanged();
} else if (data.type === 'resourceRegistered') {
debouncedResourceListChanged();
} else if (data.type === 'promptRegistered') {
debouncedPromptListChanged();
} else if (data.type === 'welcome') {
// Welcome message from the server, we're already connected to the MCP path
console.error(`Connected to path: ${data.channel}`);
} else if (data.type === 'pong') {
// Pong response
console.error(`Received pong with timestamp: ${data.timestamp}`);
} else if (data.type === 'error') {
// Error message
console.error(`Received error: ${data.message}`);
}
} catch (error) {
console.error('Error processing WebSocket message:', error);
}
}
// Function to connect to the WebSocket server
function connectToWebSocketServer(serverToken) {
// Connect to the MCP path directly with server token
const serverUrl = `ws://localhost:${CONFIG.port}${MCP_PATH}?token=${serverToken}`;
console.error(`Connecting to WebSocket server at ${MCP_PATH} with authentication...`);
wsClient = new WebSocket(serverUrl);
// Handle connection opening
wsClient.on('open', () => {
console.error(`Connected to WebSocket server on path: ${MCP_PATH}`);
});
// Handle incoming messages
wsClient.on('message', (message) => {
handleWebSocketMessage(message);
});
// Handle connection closing
wsClient.on('close', (code, reason) => {
console.error(`WebSocket connection closed: ${code} ${reason}`);
wsClient = null;
// Try to reconnect after a delay
setTimeout(connectToWebSocketServer, 5000);
});
// Handle connection errors
wsClient.on('error', (error) => {
console.error('WebSocket connection error:', error);
});
}
// Function to send a message to the WebSocket server
function sendMessage(message) {
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
console.error('Cannot send message: WebSocket not connected');
return Promise.reject(new Error('WebSocket not connected'));
}
try {
wsClient.send(JSON.stringify(message));
return Promise.resolve();
} catch (error) {
console.error('Error sending message:', error);
return Promise.reject(error);
}
}
// Set up the MCP server to handle tool calls by sending them to the WebSocket server
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "_webmcp_get-token") {
const token = await generateNewRegistrationToken();
// Copiar token al portapapeles del sistema
try {
execSync('clip.exe', { input: token, stdio: ['pipe', 'ignore', 'ignore'] });
} catch (e) {
try {
execSync('xclip -selection clipboard', { input: token, stdio: ['pipe', 'ignore', 'ignore'] });
} catch (e2) {
// No hay clipboard disponible
}
}
// Enviar token a los navegadores conectados para copiar al portapapeles
try {
await sendMessage({
type: 'clipboardCopy',
text: token
});
} catch (e) {
// Ignorar si no hay clientes conectados
}
return {
content: [{
type: "text",
text: `Token copiado al portapapeles.\n${token}`,
}]
};
}
if (request.params.name === "_webmcp_clear-cache") {
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('Clear cache timeout'));
}
}, 10000);
});
try {
await sendMessage({ id: requestId, type: 'clearRegistry' });
const result = await responsePromise;
await mcpServer.sendToolListChanged();
await mcpServer.sendPromptListChanged();
await mcpServer.sendResourceListChanged();
return { content: [{ type: "text", text: result }] };
} catch (e) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
if (request.params.name === "_webmcp_browser-info") {
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 de navegadores'));
}
}, 10000);
});
try {
await sendMessage({ id: requestId, type: 'getClientInfo' });
const result = await responsePromise;
return result;
} catch (e) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
if (request.params.name === "_webmcp_agregar-tool") {
if (!CONFIG.dev) {
return { content: [{ type: "text", text: "agregar-tool 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 { nombre, descripcion, codigo, parametros } = request.params.arguments;
if (!nombre || !descripcion || !codigo) {
return { content: [{ type: "text", text: "Se requieren: nombre, descripcion y codigo" }], 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 creando herramienta'));
}
}, 15000);
});
try {
await sendMessage({
id: requestId,
type: 'createTool',
name: nombre,
description: descripcion,
code: codigo,
parametros: parametros
});
const result = await responsePromise;
await mcpServer.sendToolListChanged();
return result;
} catch (e) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
if (request.params.name === "_webmcp_quitar-tool") {
if (!CONFIG.dev) {
return { content: [{ type: "text", text: "quitar-tool 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 { nombre, listar, todas } = request.params.arguments || {};
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'));
}
}, 10000);
});
try {
await sendMessage({
id: requestId,
type: 'removeTool',
name: nombre,
listar: !!listar,
todas: !!todas
});
const result = await responsePromise;
await mcpServer.sendToolListChanged();
return result;
} catch (e) {
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 !== 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) {
return {
content: [{
type: "text",
text: "Not connected to WebSocket server"
}],
isError: true
};
}
// Create a unique request ID
const requestId = (requestIdCounter++).toString();
// Create a promise that will be resolved when we get a response
const responsePromise = new Promise((resolve, reject) => {
// Store the resolver functions
pendingRequests.set(requestId, {resolve, reject});
// Set a timeout to prevent hanging requests
setTimeout(() => {
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
reject(new Error(`Tool call timed out: ${request.params.name}`));
}
}, 30000); // 30 second timeout
});
// Send the request to the WebSocket server
try {
await sendMessage({
id: requestId,
type: 'callTool',
tool: request.params.name,
arguments: request.params.arguments
});
// Wait for the response
return await responsePromise;
} catch (error) {
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
// Set up the MCP server to handle list tools by querying the WebSocket server
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
const builtInTools = [
{
name: "_webmcp_get-token",
description: "Genera un token de conexion para vincular un navegador con @nucleoriofrio/webmcp. El usuario puede decir 'registrar token' o 'conectar webmcp'.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "_webmcp_clear-cache",
description: "Limpia todas las herramientas, prompts y recursos registrados en el cache del servidor @nucleoriofrio/webmcp. Usar para forzar un estado limpio.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "_webmcp_browser-info",
description: "Muestra informacion de los navegadores conectados: user agent, URL, hostname, idioma, resolucion y tiempo de conexion.",
inputSchema: {
type: "object",
properties: {},
},
},
];
// Tools de desarrollo: solo disponibles con --dev o WEBMCP_DEV=true
if (CONFIG.dev) {
builtInTools.push(
{
name: "_webmcp_agregar-tool",
description: "[DEV] Registra una nueva herramienta dinamicamente. El agente puede usar esto para crear herramientas nuevas en tiempo real.",
inputSchema: {
type: "object",
properties: {
nombre: { type: "string", description: "Nombre de la herramienta" },
descripcion: { type: "string", description: "Descripcion de lo que hace" },
parametros: { type: "string", description: "JSON string con las properties del schema, ej: {\"msg\":{\"type\":\"string\",\"description\":\"mensaje\"}}" },
codigo: { type: "string", description: "Codigo JavaScript del body de la funcion. Recibe \"args\" como parametro. Debe retornar un string." }
},
required: ["nombre", "descripcion", "codigo"]
},
},
{
name: "_webmcp_quitar-tool",
description: "[DEV] Desregistra herramientas. Usa listar=true para ver las disponibles, todas=true para quitar todas, o nombre para quitar una especifica.",
inputSchema: {
type: "object",
properties: {
nombre: { type: "string", description: "Nombre de la herramienta a quitar" },
listar: { type: "boolean", description: "Si es true, lista las herramientas en vez de quitar" },
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: {},
},
}
);
}
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
return {tools: builtInTools};
}
// Create a unique request ID
const requestId = (requestIdCounter++).toString();
// Create a promise that will be resolved when we get a response
const responsePromise = new Promise((resolve, reject) => {
// Store the resolver functions
pendingRequests.set(requestId, {resolve, reject});
// Set a timeout to prevent hanging requests
setTimeout(() => {
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
reject(new Error('List tools request timed out'));
}
}, 10000); // 10 second timeout
});
// Send the request to the WebSocket server
try {
await sendMessage({
id: requestId,
type: 'listTools'
});
const tools = await responsePromise;
// Wait for the response
return { tools: [...tools, ...builtInTools] };
} catch (error) {
console.error('Error listing tools:', error);
return {tools: []}; // Return empty list on error
}
});
// Set up the MCP server to handle list prompts by querying the WebSocket server
mcpServer.setRequestHandler(ListPromptsRequestSchema, async () => {
const builtInPrompts = [];
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
return {
prompts: builtInPrompts
};
}
// Create a unique request ID
const requestId = (requestIdCounter++).toString();
// Create a promise that will be resolved when we get a response
const responsePromise = new Promise((resolve, reject) => {
// Store the resolver functions
pendingRequests.set(requestId, {resolve, reject});
// Set a timeout to prevent hanging requests
setTimeout(() => {
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
reject(new Error('List prompts request timed out'));
}
}, 10000); // 10 second timeout
});
// Send the request to the WebSocket server
try {
await sendMessage({
id: requestId,
type: 'listPrompts'
});
const prompts = await responsePromise;
// Wait for the response
return {
prompts: [
...prompts,
...builtInPrompts
],
};
} catch (error) {
console.error('Error listing prompts:', error);
return {prompts: []}; // Return empty list on error
}
});
// Set up the MCP server to handle get prompt by querying the WebSocket server
mcpServer.setRequestHandler(GetPromptRequestSchema, async (request) => {
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
throw new Error("Not connected to WebSocket server");
}
// Create a unique request ID
const requestId = (requestIdCounter++).toString();
// Create a promise that will be resolved when we get a response
const responsePromise = new Promise((resolve, reject) => {
// Store the resolver functions
pendingRequests.set(requestId, {resolve, reject});
// Set a timeout to prevent hanging requests
setTimeout(() => {
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
reject(new Error(`Get prompt request timed out: ${request.params.name}`));
}
}, 30000); // 30 second timeout
});
// Send the request to the WebSocket server
try {
await sendMessage({
id: requestId,
type: 'getPrompt',
name: request.params.name,
arguments: request.params.arguments
});
// Wait for the response
return await responsePromise;
} catch (error) {
console.error('Error getting prompt:', error);
throw error;
}
});
// Set up the MCP server to handle list resources by querying the WebSocket server
mcpServer.setRequestHandler(ListResourcesRequestSchema, async () => {
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
return {resources: []};
}
// Create a unique request ID
const requestId = (requestIdCounter++).toString();
// Create a promise that will be resolved when we get a response
const responsePromise = new Promise((resolve, reject) => {
// Store the resolver functions
pendingRequests.set(requestId, {resolve, reject});
// Set a timeout to prevent hanging requests
setTimeout(() => {
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
reject(new Error('List resources request timed out'));
}
}, 10000); // 10 second timeout
});
// Send the request to the WebSocket server
try {
await sendMessage({
id: requestId,
type: 'listResources'
});
const {resources} = await responsePromise;
// Wait for the response
return {resources};
} catch (error) {
console.error('Error listing resources:', error);
return {resources: []}; // Return empty list on error
}
});
// Set up the MCP server to handle list resource templates by querying the WebSocket server
mcpServer.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
return {resourceTemplates: []};
}
// Create a unique request ID
const requestId = (requestIdCounter++).toString();
// Create a promise that will be resolved when we get a response
const responsePromise = new Promise((resolve, reject) => {
// Store the resolver functions
pendingRequests.set(requestId, {resolve, reject});
// Set a timeout to prevent hanging requests
setTimeout(() => {
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
reject(new Error('List resource templates request timed out'));
}
}, 10000); // 10 second timeout
});
// Send the request to the WebSocket server
try {
await sendMessage({
id: requestId,
type: 'listResources'
});
const {resourceTemplates} = await responsePromise;
// Wait for the response
return {resourceTemplates};
} catch (error) {
console.error('Error listing resource templates:', error);
return {resourceTemplates: []}; // Return empty list on error
}
});
// Set up the MCP server to handle read resource by querying the WebSocket server
mcpServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
throw new Error("Not connected to WebSocket server");
}
// Create a unique request ID
const requestId = (requestIdCounter++).toString();
// Create a promise that will be resolved when we get a response
const responsePromise = new Promise((resolve, reject) => {
// Store the resolver functions
pendingRequests.set(requestId, {resolve, reject});
// Set a timeout to prevent hanging requests
setTimeout(() => {
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
reject(new Error(`Read resource request timed out: ${request.params.uri}`));
}
}, 30000); // 30 second timeout
});
// Send the request to the WebSocket server
try {
await sendMessage({
id: requestId,
type: 'readResource',
uri: request.params.uri
});
// Wait for the response
return await responsePromise;
} catch (error) {
console.error('Error reading resource:', error);
throw error;
}
});
// Set up the MCP server to handle sampling by querying the WebSocket server
mcpServer.setRequestHandler(CreateMessageRequestSchema, async (request) => {
if (!wsClient || wsClient.readyState !== WebSocket.OPEN) {
throw new Error("Not connected to WebSocket server");
}
// Create a unique request ID
const requestId = (requestIdCounter++).toString();
// Create a promise that will be resolved when we get a response
const responsePromise = new Promise((resolve, reject) => {
// Store the resolver functions
pendingRequests.set(requestId, {resolve, reject});
// Set a timeout to prevent hanging requests
setTimeout(() => {
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
reject(new Error(`Sampling request timed out`));
}
}, 120000); // 120 second timeout (sampling can take longer)
});
// Send the request to the WebSocket server with all parameters from the request
try {
await sendMessage({
id: requestId,
type: 'createSamplingMessage',
messages: request.params.messages,
systemPrompt: request.params.systemPrompt,
includeContext: request.params.includeContext,
temperature: request.params.temperature,
maxTokens: request.params.maxTokens,
stopSequences: request.params.stopSequences,
metadata: request.params.metadata,
modelPreferences: request.params.modelPreferences
});
// Wait for the response
return await responsePromise;
} catch (error) {
console.error('Error creating sampling message:', error);
throw error;
}
});
async function runMcpServer(serverToken) {
// Connect to the WebSocket server
connectToWebSocketServer(serverToken);
const transport = new StdioServerTransport();
await mcpServer.connect(transport);
console.error("@nucleoriofrio/webmcp v0.2.0 — MCP server running with stdio transport");
if (CONFIG.dev) {
console.error("[DEV] Modo desarrollo activo — agregar-tool y quitar-tool habilitados");
}
}
export { runMcpServer };