118
config.js
118
config.js
@@ -1,118 +0,0 @@
|
|||||||
import * as path from 'path';
|
|
||||||
import * as dotenv from 'dotenv';
|
|
||||||
import * as os from 'os';
|
|
||||||
import * as fs from 'fs/promises';
|
|
||||||
import envPaths from 'env-paths';
|
|
||||||
|
|
||||||
// Create config directory in user's home folder
|
|
||||||
const HOME_DIR = os.homedir();
|
|
||||||
const CONFIG_DIR = path.join(HOME_DIR, '.webmcp');
|
|
||||||
|
|
||||||
// Ensure config directory exists
|
|
||||||
const ensureConfigDir = async () => {
|
|
||||||
try {
|
|
||||||
await fs.mkdir(CONFIG_DIR, {recursive: true});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error creating config directory at ${CONFIG_DIR}:`, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Process ID file path
|
|
||||||
const PID_FILE = path.join(CONFIG_DIR, '.webmcp-server.pid');
|
|
||||||
// Environment file path
|
|
||||||
const ENV_FILE = path.join(CONFIG_DIR, '.env');
|
|
||||||
// Tokens file path
|
|
||||||
const TOKENS_FILE = path.join(CONFIG_DIR, '.webmcp-tokens.json');
|
|
||||||
|
|
||||||
// Load environment variables
|
|
||||||
dotenv.config({path: ENV_FILE});
|
|
||||||
|
|
||||||
// Server token for MCP authentication
|
|
||||||
const SERVER_TOKEN = process.env.WEBMCP_SERVER_TOKEN || '';
|
|
||||||
|
|
||||||
const HOST = "localhost";
|
|
||||||
|
|
||||||
const CONFIG = {};
|
|
||||||
|
|
||||||
function setConfig(args) {
|
|
||||||
Object.entries(args).forEach(([key, value]) => {
|
|
||||||
CONFIG[key] = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatChannel(channel) {
|
|
||||||
return `/${channel.replace(/[.:]/g, '_')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function exists(somePath) {
|
|
||||||
try {
|
|
||||||
await fs.access(somePath);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function configureMcpClientWithPath(clientConfigPath) {
|
|
||||||
const directory = path.dirname(clientConfigPath);
|
|
||||||
if (!await exists(directory)) {
|
|
||||||
await fs.mkdir(directory, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const webmcpConfig = {
|
|
||||||
"webmcp": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": [
|
|
||||||
"-y",
|
|
||||||
"@nucleoriofrio/webmcp@latest",
|
|
||||||
"--mcp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let json = { mcpServers: {} };
|
|
||||||
|
|
||||||
// If one already exists, we'll want to update it
|
|
||||||
if (await exists(clientConfigPath)) {
|
|
||||||
const rawJSON = await fs.readFile(clientConfigPath);
|
|
||||||
try {
|
|
||||||
json = JSON.parse(rawJSON);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Failed to update MCP client configuration: ${e}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
json.mcpServers = { ...json.mcpServers, ...webmcpConfig};
|
|
||||||
await fs.writeFile(clientConfigPath, JSON.stringify(json, null, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
const availableClientConfigs = {
|
|
||||||
"claude": [envPaths("Claude", { suffix: "" }).data, "claude_desktop_config.json"],
|
|
||||||
"cline": [envPaths("Code", { suffix: "" }).data, "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"],
|
|
||||||
"cursor": [HOME_DIR, ".cursor", "mcp.json"],
|
|
||||||
"windsurf": [HOME_DIR, ".codeium", "windsurf", "mcp_config.json"]
|
|
||||||
};
|
|
||||||
|
|
||||||
async function configureMcpClient(clientType) {
|
|
||||||
let clientConfigPath = availableClientConfigs[clientType];
|
|
||||||
if (clientConfigPath) {
|
|
||||||
await configureMcpClientWithPath(clientConfigPath);
|
|
||||||
} else {
|
|
||||||
console.error("Unsupported client - treating it like a path...")
|
|
||||||
await configureMcpClientWithPath(clientType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
CONFIG,
|
|
||||||
HOST,
|
|
||||||
PID_FILE,
|
|
||||||
ENV_FILE,
|
|
||||||
TOKENS_FILE,
|
|
||||||
SERVER_TOKEN,
|
|
||||||
ensureConfigDir,
|
|
||||||
formatChannel,
|
|
||||||
setConfig,
|
|
||||||
configureMcpClientWithPath,
|
|
||||||
configureMcpClient,
|
|
||||||
};
|
|
||||||
889
server.js
889
server.js
@@ -1,889 +0,0 @@
|
|||||||
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 };
|
|
||||||
118
tokens.js
118
tokens.js
@@ -1,118 +0,0 @@
|
|||||||
import * as fs from 'fs/promises';
|
|
||||||
import * as crypto from 'crypto';
|
|
||||||
import {ENV_FILE, formatChannel, HOST, TOKENS_FILE,CONFIG} from "./config.js";
|
|
||||||
|
|
||||||
// Function to generate a secure random token
|
|
||||||
function generateToken() {
|
|
||||||
return crypto.randomBytes(16).toString('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Authorized channel-token pairs - Only channels with valid tokens can connect
|
|
||||||
// Format: { "/channel1": "token123" }
|
|
||||||
let authorizedTokens = {};
|
|
||||||
|
|
||||||
function getToken(channel) {
|
|
||||||
return authorizedTokens[channel];
|
|
||||||
}
|
|
||||||
|
|
||||||
function setToken(channel, value) {
|
|
||||||
authorizedTokens[channel] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteToken(channel) {
|
|
||||||
delete authorizedTokens[channel];
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearTokens(channel) {
|
|
||||||
authorizedTokens = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load authorized tokens from disk
|
|
||||||
async function loadAuthorizedTokens() {
|
|
||||||
try {
|
|
||||||
const data = await fs.readFile(TOKENS_FILE, 'utf8');
|
|
||||||
authorizedTokens = JSON.parse(data || "{}");
|
|
||||||
|
|
||||||
// console.error(`Loaded ${Object.keys(authorizedTokens).length} authorized channel-token pairs from ${TOKENS_FILE}`);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
// If file doesn't exist, start with empty tokens
|
|
||||||
if (error.code === 'ENOENT') {
|
|
||||||
authorizedTokens = {};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
console.error('Error loading authorized tokens:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save authorized tokens to disk
|
|
||||||
async function saveAuthorizedTokens() {
|
|
||||||
try {
|
|
||||||
// Convert Map to object for JSON serialization
|
|
||||||
const stringified = JSON.stringify(authorizedTokens, null, 2);
|
|
||||||
await fs.writeFile(TOKENS_FILE, stringified, 'utf8');
|
|
||||||
// console.error(`Saved ${stringified} authorized channel-token pairs to ${TOKENS_FILE}`);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saving authorized tokens:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to save server token to .env file
|
|
||||||
async function saveServerTokenToEnv(token) {
|
|
||||||
try {
|
|
||||||
let envContent = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Try to read existing .env file
|
|
||||||
envContent = await fs.readFile(ENV_FILE, 'utf8');
|
|
||||||
|
|
||||||
// Check if WEBMCP_SERVER_TOKEN is already defined
|
|
||||||
if (envContent.includes('WEBMCP_SERVER_TOKEN=')) {
|
|
||||||
// Replace the existing token
|
|
||||||
envContent = envContent.replace(/WEBMCP_SERVER_TOKEN=.*(\r?\n|$)/g, `WEBMCP_SERVER_TOKEN=${token}$1`);
|
|
||||||
} else {
|
|
||||||
// Add the token to the end
|
|
||||||
envContent += `\nWEBMCP_SERVER_TOKEN=${token}\n`;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// File doesn't exist, create new content
|
|
||||||
envContent = `WEBMCP_SERVER_TOKEN=${token}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the content to the .env file
|
|
||||||
await fs.writeFile(ENV_FILE, envContent, 'utf8');
|
|
||||||
console.error(`Server token saved to ${ENV_FILE}`);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saving server token to .env file:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateNewRegistrationToken() {
|
|
||||||
// Generate a random token for registration
|
|
||||||
const token = generateToken();
|
|
||||||
|
|
||||||
// Create a connection object with server address and token
|
|
||||||
const address = `${HOST}:${CONFIG.port}`;
|
|
||||||
const serverAddress = `ws://${address}`;
|
|
||||||
const connectionData = {
|
|
||||||
server: serverAddress,
|
|
||||||
token: token
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert to JSON and base64 encode
|
|
||||||
const jsonStr = JSON.stringify(connectionData);
|
|
||||||
const encodedData = Buffer.from(jsonStr).toString('base64');
|
|
||||||
|
|
||||||
setToken(formatChannel(address), token);
|
|
||||||
await saveAuthorizedTokens();
|
|
||||||
|
|
||||||
return encodedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {generateToken, getToken, setToken, loadAuthorizedTokens, saveAuthorizedTokens, clearTokens, deleteToken, saveServerTokenToEnv, generateNewRegistrationToken};
|
|
||||||
78
webmcp.d.ts
vendored
78
webmcp.d.ts
vendored
@@ -1,78 +0,0 @@
|
|||||||
export interface WebMCPOptions {
|
|
||||||
color?: string;
|
|
||||||
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
||||||
size?: string;
|
|
||||||
padding?: string;
|
|
||||||
inactivityTimeout?: number;
|
|
||||||
headless?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolInputSchema {
|
|
||||||
type: string;
|
|
||||||
properties: Record<string, any>;
|
|
||||||
required?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResourceOptions {
|
|
||||||
uri?: string;
|
|
||||||
uriTemplate?: string;
|
|
||||||
mimeType?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PromptArgument {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
required?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ToolHandler = (args: any) => string | object | Promise<string | object>;
|
|
||||||
export type PromptHandler = (args: any) => object | Promise<object>;
|
|
||||||
export type ResourceHandler = (uri: string) => object | Promise<object>;
|
|
||||||
|
|
||||||
export interface ConnectionInfo {
|
|
||||||
isConnected: boolean;
|
|
||||||
channel: string;
|
|
||||||
server: string;
|
|
||||||
status: string;
|
|
||||||
tools: string[];
|
|
||||||
prompts: string[];
|
|
||||||
resources: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebMCPEventMap {
|
|
||||||
connected: { channel: string; server: string };
|
|
||||||
disconnected: { code: number; reason: string };
|
|
||||||
reconnecting: { attempt: number; maxAttempts: number; delay: number };
|
|
||||||
statusChange: { status: string; message: string };
|
|
||||||
toolRegistered: { name: string };
|
|
||||||
toolCreated: { name: string };
|
|
||||||
toolRemoved: { name: string };
|
|
||||||
error: { message: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
declare class WebMCP {
|
|
||||||
readonly isConnected: boolean;
|
|
||||||
readonly isExpanded: boolean;
|
|
||||||
|
|
||||||
constructor(options?: WebMCPOptions);
|
|
||||||
|
|
||||||
connect(connectionToken: string): Promise<void>;
|
|
||||||
disconnect(): void;
|
|
||||||
|
|
||||||
on<K extends keyof WebMCPEventMap>(event: K, callback: (data: WebMCPEventMap[K]) => void): () => void;
|
|
||||||
off<K extends keyof WebMCPEventMap>(event: K, callback: (data: WebMCPEventMap[K]) => void): void;
|
|
||||||
getConnectionInfo(): ConnectionInfo;
|
|
||||||
|
|
||||||
registerTool(name: string, description: string, schema: ToolInputSchema, executeFn: ToolHandler): void;
|
|
||||||
unregisterTool(name: string): void;
|
|
||||||
unregisterAllTools(): void;
|
|
||||||
|
|
||||||
registerPrompt(name: string, description: string, args: PromptArgument[], executeFn: PromptHandler): void;
|
|
||||||
unregisterPrompt(name: string): void;
|
|
||||||
|
|
||||||
registerResource(name: string, description: string, options: ResourceOptions, provideFn: ResourceHandler): void;
|
|
||||||
unregisterResource(name: string): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WebMCP;
|
|
||||||
export { WebMCP };
|
|
||||||
Reference in New Issue
Block a user