@cgaspard/webappmcp
Version:
WebApp MCP - Model Context Protocol integration for web applications with server-side debugging tools
792 lines • 39.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.IntegratedMCPServer = void 0;
exports.webappMCP = webappMCP;
const ws_1 = require("ws");
const http_1 = require("http");
const uuid_1 = require("uuid");
const mcp_server_js_1 = require("./mcp-server.js");
const mcp_sse_server_js_1 = require("./mcp-sse-server.js");
const mcp_socket_server_js_1 = require("./mcp-socket-server.js");
const logger_interceptors_js_1 = require("./logger-interceptors.js");
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
function webappMCP(config = {}) {
const { wsPort = 4835, wsHost = '0.0.0.0', // Bind to all interfaces by default
appPort = parseInt(process.env.PORT || '3000'), // Express app port
transport = 'sse', // Default to sse transport
socketPath = process.env.MCP_SOCKET_PATH || '/tmp/webapp-mcp.sock', authentication = { enabled: false }, permissions = {
read: true,
write: true,
screenshot: true,
state: true,
serverExec: process.env.NODE_ENV !== 'production', // Disabled in production by default
}, cors: corsOptions = {
origin: '*',
credentials: true,
}, mcpEndpointPath = '/mcp/sse', debug = false, // Default to no debug logging
plugins = [], // Default to no plugins
screenshotDir = '.webappmcp/screenshots', // Default screenshot directory
captureServerLogs = true, // Default to capturing server logs
serverLogLimit = 1000, // Default limit for server logs
serverTools = process.env.NODE_ENV !== 'production', // Disabled in production by default
logCapture = {
console: true, // Default to capturing console logs
streams: true, // Default to capturing streams
winston: true, // Default to capturing Winston
bunyan: true, // Default to capturing Bunyan
pino: true, // Default to capturing Pino
debug: true, // Default to capturing debug
log4js: true, // Default to capturing log4js
}, } = config;
// Logger helper that respects debug setting
const log = (...args) => {
if (debug) {
console.log('[webappmcp]', ...args);
}
};
const logError = (...args) => {
// Always log errors regardless of debug setting
console.error('[webappmcp]', ...args);
};
const clients = new Map();
let wss = null;
let mcpSSEServer = null;
// Server console log storage
const serverLogs = [];
// Setup server console interception if enabled
if (captureServerLogs) {
// First, try to intercept known logging libraries based on config
const interceptedLoggers = (0, logger_interceptors_js_1.interceptAllLoggers)(serverLogs, serverLogLimit, logCapture);
if (interceptedLoggers.length > 0) {
log(`Intercepted logging libraries: ${interceptedLoggers.join(', ')}`);
}
// Conditionally intercept console methods
if (logCapture.console) {
const originalConsole = {
log: console.log,
info: console.info,
warn: console.warn,
error: console.error,
};
const interceptor = (level, originalMethod) => {
return (...args) => {
// Don't capture our own webappmcp logs to avoid infinite loops
const isOwnLog = args.length > 0 && args[0] === '[webappmcp]';
if (!isOwnLog) {
serverLogs.push({
level,
timestamp: new Date().toISOString(),
args: args.map((arg) => {
try {
return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
}
catch {
return String(arg);
}
}),
});
// Maintain circular buffer
if (serverLogs.length > serverLogLimit) {
serverLogs.shift();
}
}
// Call original method
originalMethod.apply(console, args);
};
};
console.log = interceptor('log', originalConsole.log);
console.info = interceptor('info', originalConsole.info);
console.warn = interceptor('warn', originalConsole.warn);
console.error = interceptor('error', originalConsole.error);
}
// Conditionally intercept process.stdout/stderr
if (logCapture.streams) {
(0, logger_interceptors_js_1.interceptProcessStreams)(serverLogs, serverLogLimit);
}
// Build status message
const captureTypes = [];
if (interceptedLoggers.length > 0)
captureTypes.push(`libraries (${interceptedLoggers.join(', ')})`);
if (logCapture.console)
captureTypes.push('console');
if (logCapture.streams)
captureTypes.push('streams');
log(`Server log capture enabled with: ${captureTypes.join(', ')}`);
}
// Store reference to execute tools
let executeToolFunction = null;
// Create WebSocket server immediately, not in middleware
const server = (0, http_1.createServer)();
wss = new ws_1.WebSocketServer({ server });
let isListening = false;
wss.on('connection', (ws, request) => {
const clientId = (0, uuid_1.v4)();
if (authentication.enabled && authentication.token) {
// Check Authorization header first
const authHeader = request.headers.authorization;
// Also check URL parameter as fallback (browsers can't set WebSocket headers)
const url = new URL(request.url || '', `http://localhost`);
const tokenParam = url.searchParams.get('token');
const providedToken = authHeader?.replace('Bearer ', '') || tokenParam;
if (!providedToken || providedToken !== authentication.token) {
ws.close(1008, 'Unauthorized');
return;
}
}
const client = {
id: clientId,
ws,
url: request.url || '',
connectedAt: new Date(),
};
clients.set(clientId, client);
log(`WebApp MCP client connected: ${clientId}`);
ws.on('message', async (data) => {
try {
const message = JSON.parse(data.toString());
await handleClientMessage(client, message, permissions, clients, log);
}
catch (error) {
logError('Error handling WebSocket message:', error);
ws.send(JSON.stringify({
type: 'error',
error: error instanceof Error ? error.message : 'Unknown error',
}));
}
});
ws.on('close', () => {
clients.delete(clientId);
log(`WebApp MCP client disconnected: ${clientId}`);
});
ws.on('error', (error) => {
logError(`WebSocket error for client ${clientId}:`, error);
});
ws.send(JSON.stringify({
type: 'connected',
clientId,
permissions,
}));
// Send plugin extensions to the client
for (const plugin of plugins) {
if (plugin.clientExtensions) {
for (const extension of plugin.clientExtensions) {
if (extension.timing === 'onConnect' || !extension.timing) {
log(`Sending client extension from plugin ${plugin.name} to client ${clientId}`);
ws.send(JSON.stringify({
type: 'plugin_extension',
extension: {
pluginName: plugin.name,
code: extension.code,
dependencies: extension.dependencies,
},
}));
}
}
}
}
});
// Start the WebSocket server if not already listening
if (!isListening) {
server.listen(wsPort, wsHost, async () => {
isListening = true;
log(`WebApp MCP WebSocket server listening on ${wsHost}:${wsPort}`);
log(`=== STARTING MCP TRANSPORT CONFIGURATION ===`);
log(`Transport type: ${transport}`);
// Start MCP transport based on configuration
if (transport === 'stdio') {
log('Starting integrated stdio MCP server...');
const mcpServer = new mcp_server_js_1.IntegratedMCPServer({
wsUrl: `ws://${wsHost === '0.0.0.0' ? 'localhost' : wsHost}:${wsPort}`,
authToken: authentication.token,
});
try {
await mcpServer.start();
log('Stdio MCP transport started successfully');
}
catch (error) {
logError('Failed to start stdio MCP server:', error);
process.exit(1); // Exit on stdio failure since it's the only transport
}
}
else if (transport === 'sse') {
log('=== INITIALIZING MCP SSE SERVER ===');
log(`SSE endpoint will be available at: ${mcpEndpointPath}`);
log(`WebSocket URL: ws://${wsHost === '0.0.0.0' ? 'localhost' : wsHost}:${wsPort}`);
log(`Auth token: ${authentication.token}`);
mcpSSEServer = new mcp_sse_server_js_1.MCPSSEServer({
debug: debug,
getClients: () => {
return Array.from(clients.entries()).map(([id, client]) => ({
id,
url: client.url,
connectedAt: client.connectedAt,
type: client.url === 'mcp-server' ? 'mcp-server' : 'browser',
}));
},
executeTool: executeToolFunction || undefined,
plugins: plugins,
getServerLogs: captureServerLogs ? (level, limit, regex) => {
let logs = serverLogs;
if (level && level !== 'all') {
logs = logs.filter((log) => log.level === level);
}
// Apply regex filtering if provided
if (regex) {
try {
const pattern = new RegExp(regex);
logs = logs.filter((log) => {
// Concatenate all log arguments into a single string for matching
const logMessage = log.args.join(' ');
return pattern.test(logMessage);
});
}
catch (e) {
// Return empty array on invalid regex
return [];
}
}
return logs.slice(-(limit || 100));
} : undefined,
});
try {
log('Calling mcpSSEServer.initialize()...');
await mcpSSEServer.initialize();
log('✅ SSE MCP transport initialized successfully');
log(`✅ SSE endpoint registered at: ${mcpEndpointPath}`);
log(`✅ MCPSSEServer instance created: ${mcpSSEServer ? 'Yes' : 'No'}`);
log('✅ Ready to accept MCP connections');
// Display the MCP URL with configured port
const displayHost = wsHost === '0.0.0.0' ? 'localhost' : wsHost;
console.log('\n' + '='.repeat(60));
console.log('🚀 MCP Server SSE Interface Ready!');
console.log('='.repeat(60));
console.log(`📡 Add this URL to your AI tool's MCP configuration:`);
console.log(` http://${displayHost}:${appPort}${mcpEndpointPath}`);
console.log('');
console.log(` Note: If your Express app runs on a different port,`);
console.log(` update the URL or set appPort in the config.`);
console.log('='.repeat(60) + '\n');
}
catch (error) {
logError('❌ Failed to initialize MCP SSE server:', error);
}
}
else if (transport === 'socket') {
log('Starting MCP Unix socket server...');
const mcpSocketServer = new mcp_socket_server_js_1.MCPSocketServer({
socketPath: socketPath,
wsUrl: `ws://${wsHost === '0.0.0.0' ? 'localhost' : wsHost}:${wsPort}`,
authToken: authentication.token,
getClients: () => {
return Array.from(clients.entries()).map(([id, client]) => ({
id,
url: client.url,
connectedAt: client.connectedAt,
type: client.url === 'mcp-server' ? 'mcp-server' : 'browser',
}));
},
});
try {
await mcpSocketServer.start();
log('Unix socket MCP transport started successfully');
}
catch (error) {
logError('Failed to start MCP socket server:', error);
process.exit(1); // Exit on socket failure since it's the only transport
}
}
else if (transport === 'none') {
log('No MCP transport configured');
}
}).on('error', (err) => {
if (err.code === 'EADDRINUSE') {
log(`WebSocket port ${wsPort} is already in use, skipping WebSocket server creation`);
isListening = true; // Prevent further attempts
}
else {
logError('WebSocket server error:', err);
}
});
}
// Always set up tool execution for integrated MCP servers
executeToolFunction = async (toolName, args, clientId) => {
// Handle server-side tools first (no client connection needed)
if (['console_get_server_logs', 'server_execute_js', 'server_get_system_info', 'server_get_env'].includes(toolName)) {
return new Promise((resolve, reject) => {
// These tools are handled directly in the server-side logic below
const timeout = setTimeout(() => {
reject(new Error('Tool execution timeout'));
}, 30000);
// Handle server-side tools that don't require client connection
if (toolName === 'console_get_server_logs') {
clearTimeout(timeout);
const { level = 'all', limit = 100, regex } = args;
let logs = serverLogs;
if (level !== 'all') {
logs = logs.filter((log) => log.level === level);
}
// Apply regex filtering if provided
if (regex) {
try {
const pattern = new RegExp(regex);
logs = logs.filter((log) => {
// Concatenate all log arguments into a single string for matching
const logMessage = log.args.join(' ');
return pattern.test(logMessage);
});
}
catch (e) {
reject(new Error(`Invalid regex pattern: ${regex}`));
return;
}
}
resolve({ logs: logs.slice(-limit) });
return;
}
// Handle server_execute_js tool
if (toolName === 'server_execute_js') {
clearTimeout(timeout);
if (!serverTools || !permissions.serverExec) {
reject(new Error('Server-side JavaScript execution is disabled. Enable serverTools and permissions.serverExec in config.'));
return;
}
const { code, timeout: execTimeout = 5000 } = args;
try {
// Create a sandboxed context with limited globals
const vm = require('vm');
const sandbox = {
console,
process: {
version: process.version,
platform: process.platform,
arch: process.arch,
uptime: process.uptime,
memoryUsage: process.memoryUsage,
cpuUsage: process.cpuUsage,
},
require: (module) => {
// Only allow specific safe modules
const allowedModules = ['os', 'path', 'url', 'querystring', 'util'];
if (allowedModules.includes(module)) {
return require(module);
}
throw new Error(`Module '${module}' is not allowed`);
},
__dirname: process.cwd(),
Date,
Math,
JSON,
parseInt,
parseFloat,
Buffer,
};
const script = new vm.Script(code);
const result = script.runInNewContext(sandbox, {
timeout: execTimeout,
displayErrors: true,
});
resolve({ result: result !== undefined ? result : 'Code executed successfully', executionTime: Date.now() });
}
catch (error) {
reject(new Error(`Execution error: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
return;
}
// Handle server_get_system_info tool
if (toolName === 'server_get_system_info') {
clearTimeout(timeout);
if (!serverTools) {
reject(new Error('Server tools are disabled. Enable serverTools in config.'));
return;
}
const memUsage = process.memoryUsage();
const cpuUsage = process.cpuUsage();
resolve({
process: {
pid: process.pid,
version: process.version,
platform: process.platform,
arch: process.arch,
uptime: process.uptime(),
cwd: process.cwd(),
},
memory: {
rss: `${(memUsage.rss / 1024 / 1024).toFixed(2)} MB`,
heapTotal: `${(memUsage.heapTotal / 1024 / 1024).toFixed(2)} MB`,
heapUsed: `${(memUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`,
external: `${(memUsage.external / 1024 / 1024).toFixed(2)} MB`,
},
cpu: {
user: `${(cpuUsage.user / 1000000).toFixed(2)}s`,
system: `${(cpuUsage.system / 1000000).toFixed(2)}s`,
},
os: {
hostname: os.hostname(),
type: os.type(),
release: os.release(),
totalMemory: `${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)} GB`,
freeMemory: `${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)} GB`,
loadAverage: os.loadavg(),
cpus: os.cpus().length,
},
});
return;
}
// Handle server_get_env tool
if (toolName === 'server_get_env') {
clearTimeout(timeout);
if (!serverTools) {
reject(new Error('Server tools are disabled. Enable serverTools in config.'));
return;
}
const { filter = [], showAll = false } = args;
// Sensitive patterns to mask
const sensitivePatterns = [
/key/i, /secret/i, /password/i, /token/i, /auth/i,
/credential/i, /private/i, /api/i
];
const maskValue = (key, value) => {
if (sensitivePatterns.some(pattern => pattern.test(key))) {
return value.substring(0, 3) + '*'.repeat(Math.max(0, value.length - 3));
}
return value;
};
let envVars = {};
if (filter.length > 0) {
// Return only requested variables
filter.forEach((key) => {
if (process.env[key] !== undefined) {
envVars[key] = maskValue(key, process.env[key]);
}
});
}
else if (showAll) {
// Return all non-sensitive variables
Object.entries(process.env).forEach(([key, value]) => {
if (value !== undefined) {
envVars[key] = maskValue(key, value);
}
});
}
else {
// Return a safe subset
const safeKeys = ['NODE_ENV', 'PORT', 'HOST', 'NODE_VERSION', 'npm_package_name', 'npm_package_version'];
safeKeys.forEach(key => {
if (process.env[key] !== undefined) {
envVars[key] = process.env[key];
}
});
}
resolve({ env: envVars });
return;
}
});
}
// For client-side tools, proceed with browser client connection logic
let targetClient;
if (clientId) {
// Use specific client if provided
targetClient = clients.get(clientId);
if (!targetClient) {
throw new Error(`Client ${clientId} not found`);
}
}
else {
// Find first browser client (not MCP server)
targetClient = Array.from(clients.values()).find(client => client.url && client.url !== 'mcp-server');
if (!targetClient) {
throw new Error('No browser client connected. Please open the webapp in a browser.');
}
}
return new Promise((resolve, reject) => {
const requestId = (0, uuid_1.v4)();
const timeout = setTimeout(() => {
reject(new Error('Tool execution timeout'));
}, 30000);
const messageHandler = (data) => {
const message = JSON.parse(data.toString());
if (message.requestId === requestId) {
clearTimeout(timeout);
targetClient.ws.off('message', messageHandler);
if (message.error) {
reject(new Error(message.error));
}
else {
// Handle screenshot tools specially - save to file
if (toolName === 'capture_screenshot' || toolName === 'capture_element_screenshot') {
const saveScreenshot = async () => {
try {
const result = message.result;
if (result && result.dataUrl) {
// Extract base64 data from data URL
const matches = result.dataUrl.match(/^data:image\/(\w+);base64,(.+)$/);
if (matches) {
const format = matches[1];
const base64Data = matches[2];
// Determine screenshot directory
const screenshotsPath = path.isAbsolute(screenshotDir)
? screenshotDir
: path.join(process.cwd(), screenshotDir);
// Create screenshots directory
await fs.mkdir(screenshotsPath, { recursive: true });
// Generate filename with timestamp and random suffix
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const randomSuffix = Math.random().toString(36).substring(2, 8);
const filename = `screenshot-${timestamp}-${randomSuffix}.${format}`;
const filepath = path.join(screenshotsPath, filename);
// Write file
await fs.writeFile(filepath, Buffer.from(base64Data, 'base64'));
log(`Screenshot saved to: ${filepath}`);
// Return path instead of data URL
resolve({
...result,
path: filepath,
filename: filename,
directory: screenshotsPath,
dataUrl: undefined // Remove dataUrl from response
});
}
else {
resolve(result);
}
}
else {
resolve(result);
}
}
catch (error) {
log('Error saving screenshot:', error);
resolve(message.result); // Fall back to original result
}
};
saveScreenshot();
}
else if (toolName === 'console_save_to_file') {
// Handle console log save to file
const saveConsoleLogs = async () => {
try {
const result = message.result;
if (result && result.logs) {
// Determine logs directory
const logsPath = path.isAbsolute('.webappmcp/logs')
? '.webappmcp/logs'
: path.join(process.cwd(), '.webappmcp/logs');
// Create logs directory
await fs.mkdir(logsPath, { recursive: true });
// Generate filename with timestamp and random suffix
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const randomSuffix = Math.random().toString(36).substring(2, 8);
const format = result.format || 'json';
const extension = format === 'json' ? 'json' : 'txt';
const filename = `console-logs-${timestamp}-${randomSuffix}.${extension}`;
const filepath = path.join(logsPath, filename);
// Format and write file
let fileContent;
if (format === 'json') {
fileContent = JSON.stringify(result.logs, null, 2);
}
else {
// Text format
fileContent = result.logs.map((log) => {
const timestamp = log.timestamp || new Date().toISOString();
const level = (log.level || 'log').toUpperCase();
const message = log.args ? log.args.join(' ') : '';
return `[${timestamp}] [${level}] ${message}`;
}).join('\n');
}
await fs.writeFile(filepath, fileContent, 'utf8');
log(`Console logs saved to: ${filepath}`);
// Return path and metadata
resolve({
path: filepath,
filename: filename,
directory: logsPath,
logCount: result.logs.length,
format: format
});
}
else {
resolve(result);
}
}
catch (error) {
log('Error saving console logs:', error);
resolve(message.result); // Fall back to original result
}
};
saveConsoleLogs();
}
else {
resolve(message.result);
}
}
}
};
targetClient.ws.on('message', messageHandler);
targetClient.ws.send(JSON.stringify({
type: 'execute_tool',
requestId,
tool: toolName,
args,
}));
});
};
return (req, res, next) => {
if (req.path === '/__webappmcp/status') {
return res.json({
connected: clients.size,
permissions,
wsPort,
});
}
// List all connected clients
if (req.path === '/__webappmcp/clients' && req.method === 'GET') {
const clientList = Array.from(clients.entries()).map(([id, client]) => ({
id,
url: client.url,
connectedAt: client.connectedAt,
type: client.url === 'mcp-server' ? 'mcp-server' : 'browser',
}));
return res.json({ clients: clientList });
}
// MCP SSE endpoint
if (req.path === mcpEndpointPath) {
log(`[Middleware] Request to MCP SSE endpoint: ${req.method} ${req.path}`);
if (!mcpSSEServer) {
log(`[Middleware] ERROR: MCP SSE server not initialized`);
return res.status(503).json({ error: 'MCP SSE server not initialized' });
}
return mcpSSEServer.handleSSERequest(req, res);
}
// HTTP API for MCP tools
if (req.path.startsWith('/__webappmcp/tools/') && req.method === 'POST') {
const toolName = req.path.replace('/__webappmcp/tools/', '');
const body = req.body || {};
const { clientId, ...args } = body;
if (!executeToolFunction) {
return res.status(503).json({ error: 'MCP server not available' });
}
if (clients.size === 0) {
return res.status(503).json({ error: 'No connected clients' });
}
executeToolFunction(toolName, args, clientId)
.then(result => {
res.json({ success: true, result });
})
.catch(error => {
res.status(500).json({
error: error instanceof Error ? error.message : 'Unknown error'
});
});
return;
}
// Get server logs endpoint
if (req.path === '/__webappmcp/server-logs' && req.method === 'GET') {
const { level = 'all', limit = 100 } = req.query;
let logs = serverLogs;
if (level !== 'all') {
logs = logs.filter((log) => log.level === level);
}
return res.json({ logs: logs.slice(-Number(limit)) });
}
// List available tools
if (req.path === '/__webappmcp/tools' && req.method === 'GET') {
const tools = [
'dom_query',
'dom_get_properties',
'dom_get_text',
'dom_get_html',
'interaction_click',
'interaction_type',
'interaction_scroll',
'capture_screenshot',
'capture_element_screenshot',
'state_get_variable',
'state_local_storage',
'console_get_logs',
'console_get_server_logs',
'server_execute_js',
'server_get_system_info',
'server_get_env'
];
return res.json({ tools });
}
next();
};
}
async function handleClientMessage(client, message, permissions, clients, log) {
const { type, requestId } = message;
log(`[WebSocket] Received message from client ${client.id}:`, JSON.stringify(message));
if (type === 'init') {
client.url = message.url || client.url;
log(`[WebSocket] Client ${client.id} initialized with URL: ${client.url}`);
return;
}
if (type === 'tool_response') {
// Tool responses need to be forwarded back to the MCP server that sent the request
log(`[WebSocket] Tool response from client ${client.id}:`, message);
// Forward to all MCP servers (they will filter by requestId)
clients.forEach((otherClient) => {
if (otherClient.url === 'mcp-server' || otherClient.url === 'mcp-sse-server') {
log(`[WebSocket] Forwarding tool response to MCP client ${otherClient.id}`);
otherClient.ws.send(JSON.stringify(message));
}
});
return;
}
if (type === 'execute_tool') {
// Forward execute_tool messages from MCP servers to browser clients
log(`[WebSocket] Execute tool request from ${client.id}, forwarding to browser clients`);
let browserClients = 0;
clients.forEach((otherClient) => {
if (otherClient.url !== 'mcp-server' && otherClient.url !== 'mcp-sse-server' && otherClient.id !== client.id) {
log(`[WebSocket] Forwarding execute_tool to browser client ${otherClient.id}`);
otherClient.ws.send(JSON.stringify(message));
browserClients++;
}
});
log(`[WebSocket] Forwarded execute_tool to ${browserClients} browser clients`);
return;
}
// For any other message type, just log it
log(`[WebSocket] Unhandled message type: ${type} from client ${client.id}`);
}
exports.default = webappMCP;
var mcp_server_js_2 = require("./mcp-server.js");
Object.defineProperty(exports, "IntegratedMCPServer", { enumerable: true, get: function () { return mcp_server_js_2.IntegratedMCPServer; } });
//# sourceMappingURL=index.js.map