JavaScript MCP Server

#!/usr/bin/env node /** * JS-MCP Runner - A general solution for testing the JavaScript MCP server * * This script can: * 1. Run a single JavaScript command: node js-mcp-runner.js --exec "const x = 42; return x;" * 2. Run a JavaScript file: node js-mcp-runner.js --file test.js * 3. Run TypeScript: node js-mcp-runner.js --ts "const x: number = 42; x;" * 4. Create a session: node js-mcp-runner.js --session-create mySession * 5. Execute in a session: node js-mcp-runner.js --session-exec mySession "this.x = 42; return this.x;" * 6. Get session info: node js-mcp-runner.js --session-info mySession * 7. Run multiple commands from a JSON file: node js-mcp-runner.js --batch test-batch.json */ const fs = require('fs'); const path = require('path'); const { executeJavaScript } = require('./dist/core/executor'); const { SessionManager } = require('./dist/core/session-manager'); const { typescriptTranspiler } = require('./dist/core/typescript-transpiler'); const { packageManager } = require('./dist/core/package-manager'); // Initialize core components const sessionManager = new SessionManager(); let sessions = new Map(); // Track named sessions // Initialize package manager packageManager.initialize().catch(error => { console.error('Failed to initialize package manager:', error); }); // Parse command line arguments const args = process.argv.slice(2); let command = null; let params = {}; if (args.length === 0) { printHelp(); process.exit(0); } // Process arguments for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '--help' || arg === '-h') { printHelp(); process.exit(0); } else if (arg === '--exec' || arg === '-e') { command = 'execute'; params.code = args[++i]; } else if (arg === '--file' || arg === '-f') { command = 'file'; params.filename = args[++i]; } else if (arg === '--ts' || arg === '-t') { command = 'typescript'; params.code = args[++i]; } else if (arg === '--session-create' || arg === '-sc') { command = 'session-create'; params.name = args[++i]; } else if (arg === '--session-exec' || arg === '-se') { command = 'session-execute'; params.name = args[++i]; params.code = args[++i]; } else if (arg === '--session-info' || arg === '-si') { command = 'session-info'; params.name = args[++i]; } else if (arg === '--batch' || arg === '-b') { command = 'batch'; params.filename = args[++i]; } else if (arg === '--install-package' || arg === '-ip') { command = 'install-package'; params.name = args[++i]; if (i + 1 < args.length && !args[i + 1].startsWith('--')) { params.version = args[++i]; } } else if (arg === '--find-package' || arg === '-fp') { command = 'find-package'; params.name = args[++i]; } else if (arg === '--list-sessions' || arg === '-ls') { command = 'list-sessions'; } else { console.error(`Unknown argument: ${arg}`); printHelp(); process.exit(1); } } // Load saved sessions if they exist loadSessions(); // Execute the command executeCommand(command, params) .then(() => { // Save sessions for future use saveSessions(); process.exit(0); }) .catch(error => { console.error('Command execution failed:', error); process.exit(1); }); /** * Execute a command with parameters */ async function executeCommand(command, params) { switch (command) { case 'execute': return executeCode(params.code); case 'file': return executeFile(params.filename); case 'typescript': return executeTypeScript(params.code); case 'session-create': return createSession(params.name); case 'session-execute': return executeInSession(params.name, params.code); case 'session-info': return showSessionInfo(params.name); case 'list-sessions': return listSessions(); case 'batch': return executeBatch(params.filename); case 'install-package': return installPackage(params.name, params.version); case 'find-package': return findPackage(params.name); default: console.error('Unknown command:', command); printHelp(); process.exit(1); } } /** * Execute JavaScript code */ async function executeCode(code) { if (!code) { console.error('Error: No code provided'); return; } console.log('Executing JavaScript code...'); try { const result = await executeJavaScript(code); displayExecutionResult(result); return result; } catch (error) { console.error('Error executing code:', error); throw error; } } /** * Execute JavaScript code from a file */ async function executeFile(filename) { if (!filename) { console.error('Error: No filename provided'); return; } console.log(`Executing JavaScript file: ${filename}`); try { const code = fs.readFileSync(filename, 'utf8'); const result = await executeJavaScript(code); displayExecutionResult(result); return result; } catch (error) { console.error(`Error executing file ${filename}:`, error); throw error; } } /** * Execute TypeScript code */ async function executeTypeScript(code) { if (!code) { console.error('Error: No code provided'); return; } console.log('Transpiling TypeScript code...'); try { const transpileResult = await typescriptTranspiler.transpile(code); if (transpileResult.success && transpileResult.jsCode) { console.log('Transpilation successful!'); // Display diagnostics if any if (transpileResult.diagnostics && transpileResult.diagnostics.length > 0) { console.log('\nDiagnostics:'); transpileResult.diagnostics.forEach(d => { console.log(`- [${d.category}] ${d.message}`); }); } // Execute the transpiled JavaScript console.log('\nExecuting transpiled code...'); const jsCode = ` (function() { ${transpileResult.jsCode} // Return undefined by default if no explicit return return undefined; })(); `; const result = await executeJavaScript(jsCode); displayExecutionResult(result); return result; } else { console.log('Transpilation failed:'); console.log(transpileResult.error?.message || 'Unknown error'); if (transpileResult.diagnostics && transpileResult.diagnostics.length > 0) { console.log('\nDiagnostics:'); transpileResult.diagnostics.forEach(diagnostic => { console.log(`- [${diagnostic.category}] ${diagnostic.message}`); }); } throw new Error('TypeScript transpilation failed'); } } catch (error) { console.error('Error executing TypeScript:', error); throw error; } } /** * Create a new session */ async function createSession(name) { if (!name) { console.error('Error: Session name is required'); return; } try { const session = sessionManager.createSession(); sessions.set(name, session.id); console.log(`Session created successfully!`); console.log(`Name: ${name}`); console.log(`ID: ${session.id}`); console.log(`Created: ${session.createdAt.toISOString()}`); return session; } catch (error) { console.error('Error creating session:', error); throw error; } } /** * Execute code in a session */ async function executeInSession(name, code) { if (!name) { console.error('Error: Session name is required'); return; } if (!code) { console.error('Error: No code provided'); return; } const sessionId = sessions.get(name); if (!sessionId) { console.error(`Error: Session '${name}' not found`); return; } try { console.log(`Executing in session: ${name} (${sessionId})`); const result = await sessionManager.executeInSession(sessionId, code); displayExecutionResult(result); // Show updated variables const session = sessionManager.getSession(sessionId); if (session) { const variables = getUserVariables(session); if (variables.length > 0) { console.log('\nSession Variables:'); console.log(variables.join(', ')); } } return result; } catch (error) { console.error('Error executing in session:', error); throw error; } } /** * Show session information */ async function showSessionInfo(name) { if (!name) { console.error('Error: Session name is required'); return; } const sessionId = sessions.get(name); if (!sessionId) { console.error(`Error: Session '${name}' not found`); return; } try { const session = sessionManager.getSession(sessionId); if (!session) { console.error(`Error: Session ${name} (${sessionId}) not found in manager`); return; } console.log(`Session: ${name}`); console.log(`ID: ${session.id}`); console.log(`Created: ${session.createdAt.toISOString()}`); console.log(`Last Accessed: ${session.lastAccessedAt.toISOString()}`); console.log(`History Entries: ${session.history.length}`); // Display variables console.log('\nVariables:'); const variables = getDetailedUserVariables(session); if (variables.length === 0) { console.log(' No user-defined variables'); } else { variables.forEach(variable => { console.log(` ${variable}`); }); } // Display recent history if (session.history.length > 0) { console.log('\nRecent History:'); // Show most recent entries first (up to 5) const recentHistory = [...session.history].reverse().slice(0, 5); recentHistory.forEach((entry, index) => { const actualIndex = session.history.length - index; const codePreview = entry.code.length > 50 ? entry.code.substring(0, 47) + '...' : entry.code; console.log(` [${actualIndex}] ${codePreview}`); console.log(` Result: ${entry.result.success ? (typeof entry.result.result === 'undefined' ? 'undefined' : JSON.stringify(entry.result.result).substring(0, 50)) : 'Error: ' + entry.result.error?.message}`); }); } return session; } catch (error) { console.error('Error getting session info:', error); throw error; } } /** * List all sessions */ async function listSessions() { try { console.log('Active Sessions:'); if (sessions.size === 0) { console.log(' No active sessions'); return []; } for (const [name, id] of sessions.entries()) { const session = sessionManager.getSession(id); if (session) { console.log(`- ${name}: ${id} (${session.history.length} history entries)`); } else { console.log(`- ${name}: ${id} (session no longer exists)`); } } return Array.from(sessions.entries()); } catch (error) { console.error('Error listing sessions:', error); throw error; } } /** * Execute a batch of commands from a JSON file */ async function executeBatch(filename) { if (!filename) { console.error('Error: No filename provided'); return; } console.log(`Executing batch file: ${filename}`); try { const batchContent = fs.readFileSync(filename, 'utf8'); const batch = JSON.parse(batchContent); if (!Array.isArray(batch)) { console.error('Error: Batch file must contain an array of commands'); return; } for (let i = 0; i < batch.length; i++) { const command = batch[i]; console.log(`\nExecuting command ${i + 1}/${batch.length}: ${command.type}`); switch (command.type) { case 'execute': await executeCode(command.code); break; case 'file': await executeFile(command.filename); break; case 'typescript': await executeTypeScript(command.code); break; case 'session-create': await createSession(command.name); break; case 'session-execute': await executeInSession(command.name, command.code); break; case 'session-info': await showSessionInfo(command.name); break; case 'list-sessions': await listSessions(); break; case 'find-package': await findPackage(command.name); break; case 'install-package': await installPackage(command.name, command.version); break; case 'sleep': console.log(`Sleeping for ${command.ms}ms...`); await new Promise(resolve => setTimeout(resolve, command.ms)); break; default: console.error(`Unknown command type: ${command.type}`); } } console.log('\nBatch execution completed!'); } catch (error) { console.error(`Error executing batch file ${filename}:`, error); throw error; } } /** * Install an NPM package */ async function installPackage(name, version) { if (!name) { console.error('Error: Package name is required'); return; } console.log(`Installing package: ${name}${version ? ` version ${version}` : ''}`); try { const result = await packageManager.installPackage(name, version); if (result.success) { console.log(`Successfully installed package: ${name}${version ? ` version ${version}` : ''}`); console.log(`Operation took ${result.operationTime}ms`); } else { console.error(`Failed to install package: ${name}`); if (result.error) console.error(result.error.message); } return result; } catch (error) { console.error('Error installing package:', error); throw error; } } /** * Find a package */ async function findPackage(name) { if (!name) { console.error('Error: Package name is required'); return; } console.log(`Checking if package is installed: ${name}`); try { const packagePath = await packageManager.findPackage(name); if (packagePath) { console.log(`Package ${name} is installed at: ${packagePath}`); } else { console.log(`Package ${name} is not installed`); } return packagePath; } catch (error) { console.error('Error finding package:', error); throw error; } } /** * Display execution result */ function displayExecutionResult(result) { if (result.success) { console.log('Execution successful!'); // Display console output if any if (result.consoleOutput && result.consoleOutput.trim()) { console.log('\nConsole Output:'); console.log(result.consoleOutput.trim()); } // Display return value console.log('\nReturn Value:'); console.log(`Type: ${result.resultType}`); try { if (typeof result.result === 'undefined') { console.log('Value: undefined'); } else { const formatted = JSON.stringify(result.result, null, 2); console.log(`Value: ${formatted}`); } } catch (err) { console.log(`Value: ${String(result.result)}`); } console.log(`\nExecution Time: ${result.executionTime}ms`); } else { console.log('Execution failed:'); console.log(result.error?.message || 'Unknown error'); if (result.error?.stack) { console.log('\nStack Trace:'); console.log(result.error.stack); } } } /** * Helper function to get user variables from a session */ function getUserVariables(session) { const context = session.context; const userVars = []; // Check _userVariables first if (context._userVariables && typeof context._userVariables === 'object') { userVars.push(...Object.keys(context._userVariables)); } // If no variables in _userVariables, check context directly if (userVars.length === 0) { for (const key in context) { if (!key.startsWith('_') && typeof context[key] !== 'function' && !['global', 'queueMicrotask', 'clearImmediate', 'setImmediate', 'structuredClone', 'clearInterval', 'clearTimeout', 'setInterval', 'setTimeout', 'atob', 'btoa', 'performance', 'fetch', 'console'].includes(key)) { userVars.push(key); } } } return userVars; } /** * Helper function to get detailed user variables from a session */ function getDetailedUserVariables(session) { const context = session.context; const variables = []; // Look for the _userVariables property specifically if (context && context._userVariables && typeof context._userVariables === 'object') { const userVars = context._userVariables; for (const [key, value] of Object.entries(userVars)) { let valueStr; try { valueStr = JSON.stringify(value); if (valueStr.length > 100) { valueStr = valueStr.substring(0, 97) + '...'; } } catch (err) { valueStr = String(value); } variables.push(`${key}: ${valueStr} (${typeof value})`); } // If we found variables, return them directly if (variables.length > 0) { return variables; } } // Fallback: check for direct properties in the context for (const key in context) { if (!key.startsWith('_') && typeof context[key] !== 'function' && !['global', 'queueMicrotask', 'clearImmediate', 'setImmediate', 'structuredClone', 'clearInterval', 'clearTimeout', 'setInterval', 'setTimeout', 'atob', 'btoa', 'performance', 'fetch', 'console'].includes(key)) { const value = context[key]; let valueStr; try { valueStr = JSON.stringify(value); if (valueStr.length > 100) { valueStr = valueStr.substring(0, 97) + '...'; } } catch (err) { valueStr = String(value); } variables.push(`${key}: ${valueStr} (${typeof value})`); } } return variables; } /** * Save sessions to a file for persistence */ function saveSessions() { try { const sessionData = {}; // Save only the name-to-id mapping for (const [name, id] of sessions.entries()) { sessionData[name] = id; } // Save to current directory to ensure it's accessible and writable fs.writeFileSync( '.js-mcp-sessions.json', JSON.stringify(sessionData, null, 2) ); console.log(`Saved ${sessions.size} sessions to .js-mcp-sessions.json`); } catch (error) { console.error('Error saving sessions:', error); } } /** * Load sessions from file */ function loadSessions() { try { const sessionFile = '.js-mcp-sessions.json'; if (fs.existsSync(sessionFile)) { const sessionData = JSON.parse(fs.readFileSync(sessionFile, 'utf8')); for (const [name, id] of Object.entries(sessionData)) { // Add the session ID to our map sessions.set(name, id); } console.log(`Loaded ${sessions.size} sessions from ${sessionFile}`); } } catch (error) { console.error('Error loading sessions:', error); } } /** * Print help information */ function printHelp() { console.log('JS-MCP Runner - Test the JavaScript MCP server'); console.log('\nUsage:'); console.log(' node js-mcp-runner.js [options]'); console.log('\nOptions:'); console.log(' --help, -h Show this help message'); console.log(' --exec, -e <code> Execute JavaScript code'); console.log(' --file, -f <filename> Execute JavaScript code from a file'); console.log(' --ts, -t <code> Execute TypeScript code'); console.log(' --session-create, -sc <name> Create a new session with a name'); console.log(' --session-exec, -se <name> <code> Execute code in a named session'); console.log(' --session-info, -si <name> Show information about a session'); console.log(' --list-sessions, -ls List all active sessions'); console.log(' --batch, -b <filename> Execute a batch of commands from a JSON file'); console.log(' --install-package, -ip <name> [version] Install an NPM package'); console.log(' --find-package, -fp <name> Check if a package is installed'); console.log('\nExamples:'); console.log(' node js-mcp-runner.js --exec "const x = 42; return x;"'); console.log(' node js-mcp-runner.js --session-create mySession'); console.log(' node js-mcp-runner.js --session-exec mySession "this.count = 1; return this.count;"'); console.log(' node js-mcp-runner.js --session-exec mySession "this.count++; return this.count;"'); console.log(' node js-mcp-runner.js --session-info mySession'); console.log(' node js-mcp-runner.js --batch test-batch.json'); }