terminal_start
Execute system commands programmatically by sending instructions to a terminal session within an Electron application using the Model Context Protocol (MCP).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| command | Yes |
Input Schema (JSON Schema)
{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"properties": {
"command": {
"type": "string"
}
},
"required": [
"command"
],
"type": "object"
}
Implementation Reference
- index.js:266-288 (handler)The core handler function for the 'terminal_start' MCP tool. It checks if the server is running and starts the Electron process if not, then sends the input 'command' to the local API endpoint /execute, processes the output by stripping ANSI codes, and returns the result with session ID and exit code.async ({ command }) => { try { // Check if server is running, start if not if (!(await isServerRunning())) { await startElectronProcess(); } // Create a new session const response = await axios.post(`${apiBaseUrl}/execute`, { command }); const result = response.data; // Clean up terminal output using strip-ansi const cleanOutput = stripAnsi(result.output); return { content: [{ type: "text", text: `Session ID: ${result.sessionId}\n\n ${cleanOutput}`, exitCode: result.exitCode }], //sessionId: result.sessionId }; } catch (error) { return formatErrorResponse(error); } }
- index.js:261-289 (registration)Registers the 'terminal_start' tool with the MCP server using server.tool(), specifying the tool name, input schema {command: z.string()}, and the inline handler function.server.tool( "terminal_start", { command: z.string() }, async ({ command }) => { try { // Check if server is running, start if not if (!(await isServerRunning())) { await startElectronProcess(); } // Create a new session const response = await axios.post(`${apiBaseUrl}/execute`, { command }); const result = response.data; // Clean up terminal output using strip-ansi const cleanOutput = stripAnsi(result.output); return { content: [{ type: "text", text: `Session ID: ${result.sessionId}\n\n ${cleanOutput}`, exitCode: result.exitCode }], //sessionId: result.sessionId }; } catch (error) { return formatErrorResponse(error); } } );
- index.js:263-265 (schema)Zod input schema for the terminal_start tool: requires a 'command' string.{ command: z.string() },
- index.js:127-224 (helper)Helper function startElectronProcess() that spawns an Electron process if not already running, using mutex locking to prevent multiple instances.async function startElectronProcess() { try { // Try to acquire mutex if (!(await acquireMutex())) { logger.error('Electron process is already running or failed to acquire mutex.'); return; } // Get the path to electron from node_modules const electronExecutable = process.platform === 'win32' ? 'electron.cmd' : 'electron'; const electronPath = path.join(__dirname, 'node_modules', '.bin', electronExecutable); // Set up environment variables const env = { ...process.env, ELECTRON_START_URL: 'http://localhost:3000', ELECTRON_ENABLE_LOGGING: 'true', ELECTRON_ENABLE_STACK_DUMPING: 'true', NODE_ENV: 'development' }; logger.error('Starting Electron process'); // Use npx to run electron, hiding the window with windowsHide and shell: true // Corrected spawn call: path.resolve(__dirname) is now an argument to electronPath //const electronProcess = spawn("cmd.exe", ['/c','/s', electronPath, path.resolve(__dirname)], { const electronProcess = spawn(electronPath, [path.resolve(__dirname)], { detached: true, stdio: ['ignore', 'pipe', 'pipe'], // Keep stdio pipes for logging cwd: process.cwd(), env: env, windowsHide: true, // Ensure the window is hidden shell: true // Use shell to execute npx correctly }); // Log any output from the electron process electronProcess.stdout.on('data', (data) => { logger.error('Electron stdout:', data.toString()); }); electronProcess.stderr.on('data', (data) => { logger.error('Electron stderr:', data.toString()); }); electronProcess.on('error', async (error) => { logger.error('Failed to start Electron:', error); await releaseMutex(); }); electronProcess.on('exit', async (code, signal) => { logger.error(`Electron process exited with code ${code} and signal ${signal}`); await releaseMutex(); }); // Don't unref the process immediately to ensure it starts properly setTimeout(() => { electronProcess.unref(); }, 1000); // Wait for server to be ready return new Promise((resolve, reject) => { let attempts = 0; const maxAttempts = 60; const checkServer = async () => { try { if (await isServerRunning()) { logger.error('Server is now running'); resolve(); } else { attempts++; logger.error(`Waiting for server to start... (attempt ${attempts}/${maxAttempts})`); if (attempts >= maxAttempts) { await releaseMutex(); reject(new Error('Server failed to start within timeout period')); return; } setTimeout(checkServer, 1000); } } catch (error) { logger.error('Error checking server status:', error); attempts++; if (attempts >= maxAttempts) { await releaseMutex(); reject(new Error('Server failed to start within timeout period')); return; } setTimeout(checkServer, 1000); } }; checkServer(); }); } catch (error) { logger.error('Failed to start Electron process:', error); await releaseMutex(); throw error; } }
- index.js:63-70 (helper)Helper function isServerRunning() that checks if the local API server is responsive via /health endpoint.async function isServerRunning() { try { await axios.get(`${apiBaseUrl}/health`); return true; } catch (error) { return false; } }