#
tokens: 42193/50000 37/37 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .claude
│   └── settings.local.json
├── .github
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows
│       ├── checks.yml
│       ├── ci.yml
│       ├── codeql-analysis.yml
│       └── npm-publish.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── docs
│   └── adr
│       ├── 001-command-security-levels.md
│       ├── 002-mcp-for-shell-commands.md
│       ├── 003-command-approval-workflow.md
│       └── 004-cross-platform-support.md
├── eslint.config.js
├── examples
│   └── client-example.js
├── jest.config.cjs
├── jest.setup.cjs
├── LICENSE
├── logs
│   └── .gitkeep
├── manifest.json
├── mcp-settings-example.json
├── package-lock.json
├── package.json
├── README.md
├── run.sh
├── scripts
│   └── make-executable.js
├── smithery.yaml
├── src
│   ├── index.ts
│   ├── services
│   │   └── command-service.ts
│   └── utils
│       ├── command-whitelist-utils.ts
│       ├── logger.ts
│       └── platform-utils.ts
├── super-shell-mcp.dxt
├── tests
│   ├── command-service.platform.test.js
│   └── command-service.test.js
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/logs/.gitkeep:
--------------------------------------------------------------------------------

```
1 | # This file ensures the logs directory is tracked by Git
2 | # Log files themselves are ignored via .gitignore
```

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

```
 1 | # Dependencies
 2 | node_modules/
 3 | npm-debug.log*
 4 | yarn-debug.log*
 5 | yarn-error.log*
 6 | yarn.lock
 7 | 
 8 | # Build output
 9 | build/
10 | dist/
11 | *.tsbuildinfo
12 | 
13 | # Environment variables
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 | 
20 | # IDE and editor files
21 | .idea/
22 | .vscode/
23 | *.swp
24 | *.swo
25 | .DS_Store
26 | 
27 | # Logs
28 | logs/*.log
```

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

```markdown
  1 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/cfdude-super-shell-mcp-badge.png)](https://mseep.ai/app/cfdude-super-shell-mcp)
  2 | 
  3 | # Super Shell MCP Server
  4 | 
  5 | [![smithery badge](https://smithery.ai/badge/@cfdude/super-shell-mcp)](https://smithery.ai/package/@cfdude/super-shell-mcp)
  6 | 
  7 | An MCP (Model Context Protocol) server for executing shell commands across multiple platforms (Windows, macOS, Linux). This server provides a secure way to execute shell commands with built-in whitelisting and approval mechanisms.
  8 | 
  9 | > 🎉 **Now available as a Claude Desktop Extension!** Install with one click using the `.dxt` package - no developer tools or configuration required.
 10 | 
 11 | ## Features
 12 | 
 13 | - Execute shell commands through MCP on Windows, macOS, and Linux
 14 | - Automatic platform detection and shell selection
 15 | - Support for multiple shells:
 16 |   - **Windows**: cmd.exe, PowerShell
 17 |   - **macOS**: zsh, bash, sh
 18 |   - **Linux**: bash, sh, zsh
 19 | - Command whitelisting with security levels:
 20 |   - **Safe**: Commands that can be executed without approval
 21 |   - **Requires Approval**: Commands that need explicit approval before execution
 22 |   - **Forbidden**: Commands that are explicitly blocked
 23 | - Platform-specific command whitelists
 24 | - Non-blocking approval workflow for potentially dangerous commands
 25 | - Comprehensive logging system with file-based logs
 26 | - Comprehensive command management tools
 27 | - Platform information tool for diagnostics
 28 | 
 29 | ## Installation
 30 | 
 31 | ### Option 1: Claude Desktop Extension (.dxt) - Recommended
 32 | 
 33 | **One-Click Installation for Claude Desktop:**
 34 | 
 35 | 1. **Download** the `super-shell-mcp.dxt` file from the [latest release](https://github.com/cfdude/super-shell-mcp/releases)
 36 | 2. **Quick Install**: Double-click the `.dxt` file while Claude Desktop is open
 37 |    
 38 |    **OR**
 39 |    
 40 |    **Manual Install**: 
 41 |    - Open Claude Desktop
 42 |    - Go to **Settings** > **Extensions**
 43 |    - Click **"Add Extension"**
 44 |    - Select the downloaded `super-shell-mcp.dxt` file
 45 | 
 46 | 3. **Configure** (optional): Set custom shell path if needed
 47 | 4. **Start using** - The extension is ready to use immediately!
 48 | 
 49 | ✅ **Benefits of DXT Installation:**
 50 | - No developer tools required (Node.js, Python, etc.)
 51 | - No manual configuration files
 52 | - Automatic dependency management
 53 | - One-click installation and updates
 54 | - Secure credential storage in OS keychain
 55 | 
 56 | ### Option 2: Installing via Smithery
 57 | 
 58 | To install Super Shell MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/package/@cfdude/super-shell-mcp):
 59 | 
 60 | ```bash
 61 | npx -y @smithery/cli install @cfdude/super-shell-mcp --client claude
 62 | ```
 63 | 
 64 | ### Option 3: Installing Manually
 65 | 
 66 | ```bash
 67 | # Clone the repository
 68 | git clone https://github.com/cfdude/super-shell-mcp.git
 69 | cd super-shell-mcp
 70 | 
 71 | # Install dependencies
 72 | npm install
 73 | 
 74 | # Build the project
 75 | npm run build
 76 | ```
 77 | 
 78 | ## Usage
 79 | 
 80 | ### For Claude Desktop Extension Users (.dxt)
 81 | 
 82 | If you installed using the `.dxt` extension (Option 1), **you're ready to go!** No additional configuration needed. The extension handles everything automatically:
 83 | 
 84 | - ✅ **Automatic startup** when Claude Desktop launches
 85 | - ✅ **Platform detection** and appropriate shell selection  
 86 | - ✅ **Built-in security** with command whitelisting and approval workflows
 87 | - ✅ **Optional configuration** via Claude Desktop's extension settings
 88 | 
 89 | ### For Manual Installation Users
 90 | 
 91 | If you installed manually (Option 2 or 3), you'll need to configure Claude Desktop or your MCP client:
 92 | 
 93 | #### Starting the Server Manually
 94 | 
 95 | ```bash
 96 | npm start
 97 | ```
 98 | 
 99 | Or directly:
100 | 
101 | ```bash
102 | node build/index.js
103 | ```
104 | 
105 | #### Manual Configuration for MCP Clients
106 | 
107 | For manual installations, both Roo Code and Claude Desktop use a similar configuration format for MCP servers:
108 | 
109 | ##### Using NPX (Recommended for Manual Setup)
110 | 
111 | The easiest way to use Super Shell MCP is with NPX, which automatically installs and runs the package from npm without requiring manual setup. The package is available on NPM at [https://www.npmjs.com/package/super-shell-mcp](https://www.npmjs.com/package/super-shell-mcp).
112 | 
113 | ##### Roo Code Configuration with NPX
114 | 
115 | ```json
116 | "super-shell": {
117 |   "command": "npx",
118 |   "args": [
119 |     "-y",
120 |     "super-shell-mcp"
121 |   ],
122 |   "alwaysAllow": [],
123 |   "disabled": false
124 | }
125 | ```
126 | 
127 | ##### Claude Desktop Configuration with NPX
128 | 
129 | ```json
130 | "super-shell": {
131 |   "command": "npx",
132 |   "args": [
133 |     "-y",
134 |     "super-shell-mcp"
135 |   ],
136 |   "alwaysAllow": false,
137 |   "disabled": false
138 | }
139 | ```
140 | 
141 | #### Option 2: Using Local Installation
142 | 
143 | If you prefer to use a local installation, add the following to your Roo Code MCP settings configuration file (located at `~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json`):
144 | 
145 | ```json
146 | "super-shell": {
147 |   "command": "node",
148 |   "args": [
149 |     "/path/to/super-shell-mcp/build/index.js"
150 |   ],
151 |   "alwaysAllow": [],
152 |   "disabled": false
153 | }
154 | ```
155 | 
156 | You can optionally specify a custom shell by adding a shell parameter:
157 | 
158 | ```json
159 | "super-shell": {
160 |   "command": "node",
161 |   "args": [
162 |     "/path/to/super-shell-mcp/build/index.js",
163 |     "--shell=/usr/bin/bash"
164 |   ],
165 |   "alwaysAllow": [],
166 |   "disabled": false
167 | }
168 | ```
169 | Windows 11 example
170 | ```json
171 | "super-shell": {
172 |       "command": "C:\\Program Files\\nodejs\\node.exe",
173 |       "args": [
174 |         "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npx-cli.js",
175 |         "-y",
176 |         "super-shell-mcp",
177 |         "C:\\Users\\username"
178 |       ],
179 |       "alwaysAllow": [],
180 |       "disabled": false
181 |     }
182 | ```
183 | 
184 | #### Claude Desktop Configuration
185 | 
186 | Add the following to your Claude Desktop configuration file (located at `~/Library/Application Support/Claude/claude_desktop_config.json`):
187 | 
188 | ```json
189 | "super-shell": {
190 |   "command": "node",
191 |   "args": [
192 |     "/path/to/super-shell-mcp/build/index.js"
193 |   ],
194 |   "alwaysAllow": false,
195 |   "disabled": false
196 | }
197 | ```
198 | For Windows users, the configuration file is typically located at `%APPDATA%\Claude\claude_desktop_config.json`.
199 | 
200 | ### Platform-Specific Configuration
201 | 
202 | #### Windows
203 | - Default shell: cmd.exe (or PowerShell if available)
204 | - Configuration paths:
205 |   - Roo Code: `%APPDATA%\Code\User\globalStorage\rooveterinaryinc.roo-cline\settings\cline_mcp_settings.json`
206 |   - Claude Desktop: `%APPDATA%\Claude\claude_desktop_config.json`
207 | - Shell path examples:
208 |   - cmd.exe: `C:\\Windows\\System32\\cmd.exe`
209 |   - PowerShell: `C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
210 |   - PowerShell Core: `C:\\Program Files\\PowerShell\\7\\pwsh.exe`
211 | 
212 | #### macOS
213 | - Default shell: /bin/zsh
214 | - Configuration paths:
215 |   - Roo Code: `~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json`
216 |   - Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json`
217 | - Shell path examples:
218 |   - zsh: `/bin/zsh`
219 |   - bash: `/bin/bash`
220 |   - sh: `/bin/sh`
221 | 
222 | #### Linux
223 | - Default shell: /bin/bash (or $SHELL environment variable)
224 | - Configuration paths:
225 |   - Roo Code: `~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json`
226 |   - Claude Desktop: `~/.config/Claude/claude_desktop_config.json`
227 | - Shell path examples:
228 |   - bash: `/bin/bash`
229 |   - sh: `/bin/sh`
230 |   - zsh: `/usr/bin/zsh`
231 | 
232 | 
233 | You can optionally specify a custom shell:
234 | 
235 | ```json
236 | "super-shell": {
237 |   "command": "node",
238 |   "args": [
239 |     "/path/to/super-shell-mcp/build/index.js",
240 |     "--shell=C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
241 |   ],
242 |   "alwaysAllow": false,
243 |   "disabled": false
244 | }
245 | ```
246 | 
247 | Replace `/path/to/super-shell-mcp` with the actual path where you cloned the repository.
248 | 
249 | > **Note**:
250 | > - For Roo Code: Setting `alwaysAllow` to an empty array `[]` is recommended for security reasons, as it will prompt for approval before executing any commands. If you want to allow specific commands without prompting, you can add their names to the array, for example: `"alwaysAllow": ["execute_command", "get_whitelist"]`.
251 | > - For Claude Desktop: Setting `alwaysAllow` to `false` is recommended for security reasons. Claude Desktop uses a boolean value instead of an array, where `false` means all commands require approval and `true` means all commands are allowed without prompting.
252 | >
253 | > **Important**: The `alwaysAllow` parameter is processed by the MCP client (Roo Code or Claude Desktop), not by the Super Shell MCP server itself. The server will work correctly with either format, as the client handles the approval process before sending requests to the server.
254 | 
255 | ### Available Tools
256 | The server exposes the following MCP tools:
257 | 
258 | #### `get_platform_info`
259 | 
260 | Get information about the current platform and shell.
261 | 
262 | ```json
263 | {}
264 | ```
265 | 
266 | 
267 | #### `execute_command`
268 | 
269 | Execute a shell command on the current platform.
270 | 
271 | ```json
272 | {
273 |   "command": "ls",
274 |   "args": ["-la"]
275 | }
276 | ```
277 | 
278 | #### `get_whitelist`
279 | 
280 | Get the list of whitelisted commands.
281 | 
282 | ```json
283 | {}
284 | ```
285 | 
286 | #### `add_to_whitelist`
287 | 
288 | Add a command to the whitelist.
289 | 
290 | ```json
291 | {
292 |   "command": "python3",
293 |   "securityLevel": "safe",
294 |   "description": "Run Python 3 scripts"
295 | }
296 | ```
297 | 
298 | #### `update_security_level`
299 | 
300 | Update the security level of a whitelisted command.
301 | 
302 | ```json
303 | {
304 |   "command": "python3",
305 |   "securityLevel": "requires_approval"
306 | }
307 | ```
308 | 
309 | #### `remove_from_whitelist`
310 | 
311 | Remove a command from the whitelist.
312 | 
313 | ```json
314 | {
315 |   "command": "python3"
316 | }
317 | ```
318 | 
319 | #### `get_pending_commands`
320 | 
321 | Get the list of commands pending approval.
322 | 
323 | ```json
324 | {}
325 | ```
326 | 
327 | #### `approve_command`
328 | 
329 | Approve a pending command.
330 | 
331 | ```json
332 | {
333 |   "commandId": "command-uuid-here"
334 | }
335 | ```
336 | 
337 | #### `deny_command`
338 | 
339 | Deny a pending command.
340 | 
341 | ```json
342 | {
343 |   "commandId": "command-uuid-here",
344 |   "reason": "This command is potentially dangerous"
345 | }
346 | ```
347 | 
348 | ## Default Whitelisted Commands
349 | 
350 | The server includes platform-specific command whitelists that are automatically selected based on the detected platform.
351 | 
352 | ### Common Safe Commands (All Platforms)
353 | 
354 | - `echo` - Print text to standard output
355 | 
356 | ### Unix-like Safe Commands (macOS/Linux)
357 | 
358 | - `ls` - List directory contents
359 | - `pwd` - Print working directory
360 | - `echo` - Print text to standard output
361 | - `cat` - Concatenate and print files
362 | - `grep` - Search for patterns in files
363 | - `find` - Find files in a directory hierarchy
364 | - `cd` - Change directory
365 | - `head` - Output the first part of files
366 | - `tail` - Output the last part of files
367 | - `wc` - Print newline, word, and byte counts
368 | 
369 | ### Windows-specific Safe Commands
370 | 
371 | - `dir` - List directory contents
372 | - `type` - Display the contents of a text file
373 | - `findstr` - Search for strings in files
374 | - `where` - Locate programs
375 | - `whoami` - Display current user
376 | - `hostname` - Display computer name
377 | - `ver` - Display operating system version
378 | ### Commands Requiring Approval
379 | 
380 | #### Windows Commands Requiring Approval
381 | 
382 | - `copy` - Copy files
383 | - `move` - Move files
384 | - `mkdir` - Create directories
385 | - `rmdir` - Remove directories
386 | - `rename` - Rename files
387 | - `attrib` - Change file attributes
388 | 
389 | #### Unix Commands Requiring Approval
390 | 
391 | 
392 | - `mv` - Move (rename) files
393 | - `cp` - Copy files and directories
394 | - `mkdir` - Create directories
395 | - `touch` - Change file timestamps or create empty files
396 | - `chmod` - Change file mode bits
397 | - `chown` - Change file owner and group
398 | 
399 | ### Forbidden Commands
400 | 
401 | #### Windows Forbidden Commands
402 | 
403 | - `del` - Delete files
404 | - `erase` - Delete files
405 | - `format` - Format a disk
406 | - `runas` - Execute a program as another user
407 | 
408 | #### Unix Forbidden Commands
409 | 
410 | - `rm` - Remove files or directories
411 | - `sudo` - Execute a command as another user
412 | 
413 | ## Security Considerations
414 | 
415 | - All commands are executed with the permissions of the user running the MCP server
416 | - Commands requiring approval are held in a queue until explicitly approved
417 | - Forbidden commands are never executed
418 | - The server uses Node.js's `execFile` instead of `exec` to prevent shell injection
419 | - Arguments are validated against allowed patterns when specified
420 | 
421 | ## Extending the Whitelist
422 | 
423 | You can extend the whitelist by using the `add_to_whitelist` tool. For example:
424 | 
425 | ```json
426 | {
427 |   "command": "npm",
428 |   "securityLevel": "requires_approval",
429 |   "description": "Node.js package manager"
430 | }
431 | ```
432 | 
433 | ## NPM Package Information
434 | 
435 | Super Shell MCP is available as an npm package at [https://www.npmjs.com/package/super-shell-mcp](https://www.npmjs.com/package/super-shell-mcp).
436 | 
437 | ### Benefits of Using NPX
438 | 
439 | Using the NPX method (as shown in Option 1 of the Configuration section) offers several advantages:
440 | 
441 | 1. **No Manual Setup**: No need to clone the repository, install dependencies, or build the project
442 | 2. **Automatic Updates**: Always uses the latest published version
443 | 3. **Cross-Platform Compatibility**: Works the same way on Windows, macOS, and Linux
444 | 4. **Simplified Configuration**: Shorter configuration with no absolute paths
445 | 5. **Reduced Maintenance**: No local files to manage or update
446 | 
447 | ### Using from GitHub
448 | 
449 | If you prefer to use the latest development version directly from GitHub:
450 | 
451 | ```json
452 | "super-shell": {
453 |   "command": "npx",
454 |   "args": [
455 |     "-y",
456 |     "github:cfdude/super-shell-mcp"
457 |   ],
458 |   "alwaysAllow": [],  // For Roo Code
459 |   "disabled": false
460 | }
461 | ```
462 | 
463 | ### Publishing Your Own Version
464 | 
465 | If you want to publish your own modified version to npm:
466 | 
467 | 1. Update the package.json with your details
468 | 2. Ensure the "bin" field is properly configured:
469 |    ```json
470 |    "bin": {
471 |      "super-shell-mcp": "./build/index.js"
472 |    }
473 |    ```
474 | 3. Publish to npm:
475 |    ```bash
476 |    npm publish
477 |    ```
478 | 
479 | ## NPX Best Practices
480 | 
481 | For optimal integration with MCP clients using NPX, this project follows these best practices:
482 | 
483 | 1. **Executable Entry Point**: The main file includes a shebang line (`#!/usr/bin/env node`) and is made executable during build.
484 | 
485 | 2. **Package Configuration**:
486 |    - `"type": "module"` - Ensures ES Modules are used
487 |    - `"bin"` field - Maps the command name to the entry point
488 |    - `"files"` field - Specifies which files to include when publishing
489 |    - `"prepare"` script - Ensures compilation happens on install
490 | 
491 | 3. **TypeScript Configuration**:
492 |    - `"module": "NodeNext"` - Proper ES Modules support
493 |    - `"moduleResolution": "NodeNext"` - Consistent with ES Modules
494 | 
495 | 4. **Automatic Installation and Execution**:
496 |    - The MCP client configuration uses `npx -y` to automatically install and run the package
497 |    - No terminal window is tied up as the process runs in the background
498 | 
499 | 5. **Publishing Process**:
500 |    ```bash
501 |    # Update version in package.json
502 |    npm version patch  # or minor/major as appropriate
503 |    
504 |    # Build and publish
505 |    npm publish
506 |    ```
507 | 
508 | These practices ensure the MCP server can be started automatically by the MCP client without requiring a separate terminal window, improving user experience and operational efficiency.
509 | 
510 | ## Troubleshooting
511 | 
512 | ### Cross-Platform Issues
513 | 
514 | #### Windows-Specific Issues
515 | 
516 | 1. **PowerShell Script Execution Policy**
517 |    - **Issue**: PowerShell may block script execution with the error "Execution of scripts is disabled on this system"
518 |    - **Solution**: Run PowerShell as Administrator and execute `Set-ExecutionPolicy RemoteSigned` or use the `-ExecutionPolicy Bypass` parameter when configuring the shell
519 | 
520 | 2. **Path Separators**
521 |    - **Issue**: Windows uses backslashes (`\`) in paths, which need to be escaped in JSON
522 |    - **Solution**: Use double backslashes (`\\`) in JSON configuration files, e.g., `C:\\Windows\\System32\\cmd.exe`
523 | 
524 | 3. **Command Not Found**
525 |    - **Issue**: Windows doesn't have Unix commands like `ls`, `grep`, etc.
526 |    - **Solution**: Use Windows equivalents (`dir` instead of `ls`, `findstr` instead of `grep`)
527 | 
528 | #### macOS/Linux-Specific Issues
529 | 
530 | 1. **Shell Permissions**
531 |    - **Issue**: Permission denied when executing commands
532 |    - **Solution**: Ensure the shell has appropriate permissions with `chmod +x /path/to/shell`
533 | 
534 | 2. **Environment Variables**
535 |    - **Issue**: Environment variables not available in MCP server
536 |    - **Solution**: Set environment variables in the shell's profile file (`.zshrc`, `.bashrc`, etc.)
537 | 
538 | ### General Troubleshooting
539 | 
540 | 1. **Shell Detection Issues**
541 |    - **Issue**: Server fails to detect the correct shell
542 |    - **Solution**: Explicitly specify the shell path in the configuration
543 | 
544 | 2. **Command Execution Timeout**
545 |    - **Issue**: Commands taking too long and timing out
546 |    - **Solution**: Increase the timeout value in the command service constructor
547 | 
548 | ### Logging System
549 | 
550 | The server includes a comprehensive logging system that writes logs to a file for easier debugging and monitoring:
551 | 
552 | 1. **Log File Location**
553 |    - Default: `logs/super-shell-mcp.log` in the server's directory
554 |    - The logs directory is created automatically and tracked by Git (with a .gitkeep file)
555 |    - Log files themselves are excluded from Git via .gitignore
556 |    - Contains detailed information about server operations, command execution, and approval workflow
557 | 
558 | 2. **Log Levels**
559 |    - **INFO**: General operational information
560 |    - **DEBUG**: Detailed debugging information
561 |    - **ERROR**: Error conditions and exceptions
562 | 
563 | 3. **Viewing Logs**
564 |    - Use standard file viewing commands to check logs:
565 |      ```bash
566 |      # View the entire log
567 |      cat logs/super-shell-mcp.log
568 |      
569 |      # Follow log updates in real-time
570 |      tail -f logs/super-shell-mcp.log
571 |      ```
572 | 
573 | 4. **Log Content**
574 |    - Server startup and configuration
575 |    - Command execution requests and results
576 |    - Approval workflow events (pending, approved, denied)
577 |    - Error conditions and troubleshooting information
578 | 
579 | 3. **Whitelist Management**
580 |    - **Issue**: Need to add custom commands to whitelist
581 |    - **Solution**: Use the `add_to_whitelist` tool to add commands specific to your environment
582 | 
583 | ## License
584 | 
585 | This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
586 | 
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
 1 | 
 2 | # Contributing to This Project
 3 | 
 4 | Thank you for your interest in contributing! We welcome pull requests and contributions from the community. Please take a moment to read the guidelines below to ensure a smooth collaboration process.
 5 | 
 6 | ## 📦 Getting Started
 7 | 
 8 | 1. **Fork the Repository**  
 9 |    Click the **Fork** button at the top of the repository page to create your own copy of the project.
10 | 
11 | 2. **Clone Your Fork**  
12 |    ```bash
13 |    git clone https://github.com/your-username/your-fork.git
14 |    cd your-fork
15 |    ```
16 |    
17 | 	
18 | 3.	Create a Feature Branch
19 | Create a new branch based on main for your changes:
20 | ```bash
21 | git checkout -b feature/your-feature-name
22 | ```
23 | 
24 | 
25 | 🚀 Submitting a Pull Request
26 | 
27 | Once you’re ready to share your changes:
28 | 	1.	Ensure Code Quality
29 | 	•	Run all linting checks.
30 | 	•	Use Prettier to format your code.
31 | 	•	Confirm all tests pass (if applicable).
32 | 	2.	Document Your Work
33 | 	•	Add meaningful comments to your code.
34 | 	•	Update or add documentation where necessary (e.g., README, inline comments, or docs folder).
35 | 	3.	Submit Your PR
36 | 	•	Push your branch to your fork:
37 | ```bash
38 | git push origin feature/your-feature-name
39 | ```
40 | 
41 |   * Open a Pull Request against the main branch of the original repository.
42 |   * Include a clear summary of your changes and why they are beneficial.
43 | 
44 | ✅ Code Standards
45 | 	* Follow the existing code style and formatting conventions.
46 |   * All code must pass linting and Prettier formatting before submission.
47 |   * Keep your changes focused and limited to the scope of your feature or fix.
48 | 
49 | ❤️ Be Kind
50 |   * Be respectful and constructive in code reviews and discussions.
51 |   * Ask questions if you’re unsure—collaboration is key.
52 | 
53 | We appreciate your help in improving the project for everyone. Thank you for contributing!
54 | 
```

--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Contributor Covenant Code of Conduct
  2 | 
  3 | ## Our Pledge
  4 | 
  5 | We as members, contributors, and leaders pledge to make participation in our
  6 | community a harassment-free experience for everyone, regardless of age, body
  7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
  8 | identity and expression, level of experience, education, socio-economic status,
  9 | nationality, personal appearance, race, religion, or sexual identity
 10 | and orientation.
 11 | 
 12 | We pledge to act and interact in ways that contribute to an open, welcoming,
 13 | diverse, inclusive, and healthy community.
 14 | 
 15 | ## Our Standards
 16 | 
 17 | Examples of behavior that contributes to a positive environment for our
 18 | community include:
 19 | 
 20 | * Demonstrating empathy and kindness toward other people
 21 | * Being respectful of differing opinions, viewpoints, and experiences
 22 | * Giving and gracefully accepting constructive feedback
 23 | * Accepting responsibility and apologizing to those affected by our mistakes,
 24 |   and learning from the experience
 25 | * Focusing on what is best not just for us as individuals, but for the
 26 |   overall community
 27 | 
 28 | Examples of unacceptable behavior include:
 29 | 
 30 | * The use of sexualized language or imagery, and sexual attention or
 31 |   advances of any kind
 32 | * Trolling, insulting or derogatory comments, and personal or political attacks
 33 | * Public or private harassment
 34 | * Publishing others' private information, such as a physical or email
 35 |   address, without their explicit permission
 36 | * Other conduct which could reasonably be considered inappropriate in a
 37 |   professional setting
 38 | 
 39 | ## Enforcement Responsibilities
 40 | 
 41 | Community leaders are responsible for clarifying and enforcing our standards of
 42 | acceptable behavior and will take appropriate and fair corrective action in
 43 | response to any behavior that they deem inappropriate, threatening, offensive,
 44 | or harmful.
 45 | 
 46 | Community leaders have the right and responsibility to remove, edit, or reject
 47 | comments, commits, code, wiki edits, issues, and other contributions that are
 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
 49 | decisions when appropriate.
 50 | 
 51 | ## Scope
 52 | 
 53 | This Code of Conduct applies within all community spaces, and also applies when
 54 | an individual is officially representing the community in public spaces.
 55 | Examples of representing our community include using an official e-mail address,
 56 | posting via an official social media account, or acting as an appointed
 57 | representative at an online or offline event.
 58 | 
 59 | ## Enforcement
 60 | 
 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
 62 | reported to the community leaders responsible for enforcement at
 63 | .
 64 | All complaints will be reviewed and investigated promptly and fairly.
 65 | 
 66 | All community leaders are obligated to respect the privacy and security of the
 67 | reporter of any incident.
 68 | 
 69 | ## Enforcement Guidelines
 70 | 
 71 | Community leaders will follow these Community Impact Guidelines in determining
 72 | the consequences for any action they deem in violation of this Code of Conduct:
 73 | 
 74 | ### 1. Correction
 75 | 
 76 | **Community Impact**: Use of inappropriate language or other behavior deemed
 77 | unprofessional or unwelcome in the community.
 78 | 
 79 | **Consequence**: A private, written warning from community leaders, providing
 80 | clarity around the nature of the violation and an explanation of why the
 81 | behavior was inappropriate. A public apology may be requested.
 82 | 
 83 | ### 2. Warning
 84 | 
 85 | **Community Impact**: A violation through a single incident or series
 86 | of actions.
 87 | 
 88 | **Consequence**: A warning with consequences for continued behavior. No
 89 | interaction with the people involved, including unsolicited interaction with
 90 | those enforcing the Code of Conduct, for a specified period of time. This
 91 | includes avoiding interactions in community spaces as well as external channels
 92 | like social media. Violating these terms may lead to a temporary or
 93 | permanent ban.
 94 | 
 95 | ### 3. Temporary Ban
 96 | 
 97 | **Community Impact**: A serious violation of community standards, including
 98 | sustained inappropriate behavior.
 99 | 
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 | 
106 | ### 4. Permanent Ban
107 | 
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior,  harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 | 
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 | 
115 | ## Attribution
116 | 
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 | 
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 | 
124 | [homepage]: https://www.contributor-covenant.org
125 | 
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 | 
```

--------------------------------------------------------------------------------
/mcp-settings-example.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "mac-shell": {
 4 |       "command": "node",
 5 |       "args": ["/path/to/mac-shell-mcp/build/index.js"],
 6 |       "disabled": false,
 7 |       "alwaysAllow": []
 8 |     }
 9 |   }
10 | }
```

--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # Build the project if needed
 4 | if [ ! -d "./build" ] || [ ! -f "./build/index.js" ]; then
 5 |   echo "Building project..."
 6 |   npm run build
 7 | fi
 8 | 
 9 | # Run the MCP server
10 | echo "Starting Mac Shell MCP Server..."
11 | node build/index.js
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: CI
 2 | 
 3 | on: 
 4 |   push:
 5 |   pull_request:
 6 | 
 7 | jobs:
 8 |   test:
 9 |     runs-on: ubuntu-latest
10 |     steps:
11 |       - uses: actions/checkout@v4
12 |       - uses: actions/setup-node@v4
13 |         with:
14 |           node-version: 20
15 |       - run: npm ci
16 |       - run: npm test
17 | 
```

--------------------------------------------------------------------------------
/.claude/settings.local.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "permissions": {
 3 |     "allow": [
 4 |       "Bash(gh pr create:*)",
 5 |       "Bash(gh browse:*)",
 6 |       "Bash(docker build:*)",
 7 |       "Bash(docker run:*)",
 8 |       "mcp__super-shell__execute_command",
 9 |       "mcp__git__git_status",
10 |       "mcp__git__git_add",
11 |       "mcp__git__git_commit"
12 |     ],
13 |     "deny": []
14 |   }
15 | }
```

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

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "types": ["node"],
 7 |     "esModuleInterop": true,
 8 |     "strict": true,
 9 |     "outDir": "./build",
10 |     "rootDir": "./src",
11 |     "declaration": true,
12 |     "sourceMap": true,
13 |     "skipLibCheck": true,
14 |     "forceConsistentCasingInFileNames": true
15 |   },
16 |   "include": ["src/**/*"],
17 |   "exclude": ["node_modules", "build"]
18 | }
```

--------------------------------------------------------------------------------
/jest.config.cjs:
--------------------------------------------------------------------------------

```
 1 | module.exports = {
 2 |   transform: {},
 3 |   testEnvironment: 'node',
 4 |   testMatch: ['**/tests/**/*.test.js'],
 5 |   verbose: true,
 6 |   testRunner: 'jest-circus/runner',
 7 |   transformIgnorePatterns: [
 8 |     'node_modules/(?!(.*\\.mjs$))'
 9 |   ],
10 |   setupFiles: ['./jest.setup.cjs'],
11 |   moduleNameMapper: {
12 |     '^../build/services/command-service.js$': '<rootDir>/jest.setup.cjs',
13 |     '^../build/utils/platform-utils.js$': '<rootDir>/jest.setup.cjs'
14 |   }
15 | };
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     properties: {}
 9 |     default: {}
10 |   commandFunction:
11 |     # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
12 |     |-
13 |     (config) => ({ command: 'npm', args: ['start'] })
14 |   exampleConfig: {}
15 | 
```

--------------------------------------------------------------------------------
/.github/workflows/checks.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Checks
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ main ]
 6 |   pull_request:
 7 |     branches: [ main ]
 8 | 
 9 | jobs:
10 |   lint-and-build:
11 |     runs-on: ubuntu-latest
12 |     
13 |     steps:
14 |     - uses: actions/checkout@v3
15 |     
16 |     - name: Setup Node.js
17 |       uses: actions/setup-node@v3
18 |       with:
19 |         node-version: '18'
20 |         
21 |     - name: Install dependencies
22 |       run: npm ci
23 |       
24 |     - name: Run Linter
25 |       run: npm run lint
26 |       
27 |     - name: Build Verification
28 |       run: npm run build
```

--------------------------------------------------------------------------------
/scripts/make-executable.js:
--------------------------------------------------------------------------------

```javascript
 1 | import fs from 'fs';
 2 | import { platform } from 'os';
 3 | 
 4 | const isWindows = platform() === 'win32';
 5 | const indexPath = './build/index.js';
 6 | 
 7 | if (isWindows) {
 8 |   console.log('Windows detected, skipping chmod operation');
 9 | } else {
10 |   try {
11 |     // On Unix-like systems, make the file executable
12 |     fs.chmodSync(indexPath, '755');
13 |     console.log(`Made ${indexPath} executable`);
14 |   } catch (error) {
15 |     console.error(`Error making ${indexPath} executable:`, error);
16 |     process.exit(1);
17 |   }
18 | }
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Feature request
 3 | about: Suggest an idea for this project
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 | 
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 | 
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 | 
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 | 
```

--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------

```yaml
 1 | # These are supported funding model platforms
 2 | 
 3 | github: [cfdude]
 4 | patreon: # Replace with a single Patreon username
 5 | open_collective: # Replace with a single Open Collective username
 6 | ko_fi: # Replace with a single Ko-fi username
 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
 9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: cfdude
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: ['paypal.me/cfdude', 'https://account.venmo.com/u/cfdude', 'https://cash.app/$cfdude']
16 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Bug report
 3 | about: Create a report to help us improve
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 | 
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 | 
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 | 
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 | 
26 | **Desktop (please complete the following information):**
27 |  - OS: [e.g. iOS]
28 |  - Browser [e.g. chrome, safari]
29 |  - Version [e.g. 22]
30 | 
31 | **Smartphone (please complete the following information):**
32 |  - Device: [e.g. iPhone6]
33 |  - OS: [e.g. iOS8.1]
34 |  - Browser [e.g. stock browser, safari]
35 |  - Version [e.g. 22]
36 | 
37 | **Additional context**
38 | Add any other context about the problem here.
39 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Use Node.js 18 Alpine for smaller image size
 2 | FROM node:18-alpine
 3 | 
 4 | # Set working directory
 5 | WORKDIR /app
 6 | 
 7 | # Create logs directory
 8 | RUN mkdir -p logs
 9 | 
10 | # Copy package files first for better caching
11 | COPY package*.json ./
12 | 
13 | # Copy TypeScript configuration and source code
14 | COPY tsconfig.json ./
15 | COPY src/ ./src/
16 | 
17 | # Install all dependencies (including dev dependencies for build)
18 | RUN npm ci
19 | 
20 | # Build the project (compiles TypeScript and sets executable permissions)
21 | RUN npm run build
22 | 
23 | # Remove dev dependencies to reduce image size
24 | RUN npm prune --omit=dev && npm cache clean --force
25 | 
26 | # Ensure the built file is executable
27 | RUN chmod +x build/index.js
28 | 
29 | # Expose stdio for MCP communication
30 | # Note: MCP servers typically communicate via stdio, not network ports
31 | 
32 | # Set the entrypoint to the built application
33 | ENTRYPOINT ["node", "build/index.js"]
34 | 
```

--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: "CodeQL Analysis"
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ main ]
 6 |   pull_request:
 7 |     branches: [ main ]
 8 |   schedule:
 9 |     - cron: '0 0 * * 0'  # Run once a week at midnight on Sunday
10 | 
11 | jobs:
12 |   analyze:
13 |     name: Analyze
14 |     runs-on: ubuntu-latest
15 |     permissions:
16 |       actions: read
17 |       contents: read
18 |       security-events: write
19 | 
20 |     strategy:
21 |       fail-fast: false
22 |       matrix:
23 |         language: [ 'javascript' ]  # Add other languages as needed: python, java, go, cpp, etc.
24 | 
25 |     steps:
26 |     - name: Checkout repository
27 |       uses: actions/checkout@v4
28 | 
29 |     - name: Initialize CodeQL
30 |       uses: github/codeql-action/init@v3
31 |       with:
32 |         languages: ${{ matrix.language }}
33 | 
34 |     # Autobuild attempts to build any compiled languages
35 |     - name: Autobuild
36 |       uses: github/codeql-action/autobuild@v3
37 | 
38 |     - name: Perform CodeQL Analysis
39 |       uses: github/codeql-action/analyze@v3
40 |       with:
41 |         category: "/language:${{matrix.language}}"
42 | 
```

--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------

```yaml
 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
 3 | 
 4 | name: Publish Package to npmjs
 5 | 
 6 | on:
 7 |   release:
 8 |     types: [created]
 9 | 
10 | jobs:
11 |   build:
12 |     runs-on: ubuntu-latest
13 |     permissions:
14 |       contents: read
15 |       id-token: write
16 |     steps:
17 |       - uses: actions/checkout@v4
18 |       - uses: actions/setup-node@v4
19 |         with:
20 |           node-version: 20
21 |       - run: npm ci
22 |       - run: npm test
23 | 
24 |   publish-npm:
25 |     needs: build
26 |     runs-on: ubuntu-latest
27 |     permissions:
28 |       contents: read
29 |       id-token: write
30 |     steps:
31 |       - uses: actions/checkout@v4
32 |       - uses: actions/setup-node@v4
33 |         with:
34 |           node-version: 20
35 |           registry-url: https://registry.npmjs.org/
36 |       - run: npm ci
37 |       - run: npm publish --provenance
38 |         env:
39 |           NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
40 | 
```

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

```json
 1 | {
 2 |   "name": "super-shell-mcp",
 3 |   "version": "2.0.13",
 4 |   "description": "MCP server for executing shell commands across multiple platforms",
 5 |   "type": "module",
 6 |   "main": "build/index.js",
 7 |   "bin": {
 8 |     "super-shell-mcp": "./build/index.js"
 9 |   },
10 |   "repository": {
11 |     "type": "git",
12 |     "url": "https://github.com/cfdude/super-shell-mcp.git"
13 |   },
14 |   "files": [
15 |     "build"
16 |   ],
17 |   "scripts": {
18 |     "build": "tsc && chmod +x build/index.js",
19 |     "prepare": "npm run build",
20 |     "start": "node build/index.js",
21 |     "dev": "ts-node --esm src/index.ts",
22 |     "lint": "eslint .",
23 |     "test": "jest --config=jest.config.cjs",
24 |     "test:quiet": "jest --config=jest.config.cjs --silent"
25 |   },
26 |   "keywords": [
27 |     "mcp",
28 |     "shell",
29 |     "macos",
30 |     "windows",
31 |     "linux",
32 |     "zsh",
33 |     "bash",
34 |     "powershell",
35 |     "cmd",
36 |     "terminal",
37 |     "cross-platform"
38 |   ],
39 |   "author": "",
40 |   "license": "MIT",
41 |   "dependencies": {
42 |     "@modelcontextprotocol/sdk": "^1.6.1",
43 |     "zod": "^3.22.4"
44 |   },
45 |   "devDependencies": {
46 |     "@eslint/js": "^9.30.1",
47 |     "@types/jest": "^29.5.11",
48 |     "@types/node": "^20.17.24",
49 |     "@typescript-eslint/eslint-plugin": "^8.0.0",
50 |     "@typescript-eslint/parser": "^8.0.0",
51 |     "eslint": "^9.30.1",
52 |     "jest": "^29.7.0",
53 |     "jest-circus": "^29.7.0",
54 |     "ts-node": "^10.9.2",
55 |     "typescript": "^5.3.3"
56 |   }
57 | }
```

--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "dxt_version": "0.1",
 3 |   "name": "super-shell-mcp",
 4 |   "version": "2.0.13",
 5 |   "description": "Execute shell commands across multiple platforms with built-in security controls",
 6 |   "long_description": "An MCP server for executing shell commands on Windows, macOS, and Linux with automatic platform detection, command whitelisting, and approval workflows for security.",
 7 |   "author": {
 8 |     "name": "cfdude",
 9 |     "url": "https://github.com/cfdude/super-shell-mcp"
10 |   },
11 |   "repository": {
12 |     "type": "git",
13 |     "url": "https://github.com/cfdude/super-shell-mcp"
14 |   },
15 |   "license": "MIT",
16 |   "keywords": ["shell", "commands", "security", "cross-platform", "terminal"],
17 |   "server": {
18 |     "type": "node",
19 |     "entry_point": "server/index.js",
20 |     "mcp_config": {
21 |       "command": "node",
22 |       "args": ["${__dirname}/server/index.js"],
23 |       "env": {
24 |         "CUSTOM_SHELL": "${user_config.shell_path}"
25 |       }
26 |     }
27 |   },
28 |   "tools": [
29 |     {
30 |       "name": "execute_command",
31 |       "description": "Execute shell commands with security controls"
32 |     },
33 |     {
34 |       "name": "get_whitelist",
35 |       "description": "Get list of whitelisted commands"
36 |     },
37 |     {
38 |       "name": "add_to_whitelist",
39 |       "description": "Add commands to the security whitelist"
40 |     },
41 |     {
42 |       "name": "get_platform_info",
43 |       "description": "Get current platform and shell information"
44 |     }
45 |   ],
46 |   "user_config": {
47 |     "shell_path": {
48 |       "type": "string",
49 |       "title": "Custom Shell Path",
50 |       "description": "Optional: Specify a custom shell path (e.g., /bin/zsh, C:\\Windows\\System32\\cmd.exe)",
51 |       "required": false
52 |     }
53 |   }
54 | }
```

--------------------------------------------------------------------------------
/docs/adr/001-command-security-levels.md:
--------------------------------------------------------------------------------

```markdown
 1 | # ADR 001: Command Security Levels
 2 | 
 3 | ## Status
 4 | 
 5 | Accepted
 6 | 
 7 | ## Context
 8 | 
 9 | When executing shell commands through an MCP server, there's a significant security risk if all commands are allowed without restrictions. Different commands have varying levels of potential impact on the system:
10 | 
11 | 1. Some commands are relatively safe (e.g., `ls`, `pwd`, `echo`)
12 | 2. Some commands can modify the system but in limited ways (e.g., `mkdir`, `cp`, `mv`)
13 | 3. Some commands can cause significant damage (e.g., `rm -rf`, `sudo`)
14 | 
15 | We need a mechanism to categorize commands based on their potential risk and handle them accordingly.
16 | 
17 | ## Decision
18 | 
19 | We will implement a three-tier security level system for commands:
20 | 
21 | 1. **Safe Commands**: These commands can be executed immediately without approval. They are read-only or have minimal impact on the system.
22 | 
23 | 2. **Commands Requiring Approval**: These commands can modify the system but are not inherently dangerous. They will be queued for explicit approval before execution.
24 | 
25 | 3. **Forbidden Commands**: These commands are considered too dangerous and will be rejected outright.
26 | 
27 | Each command will be categorized in a whitelist, and the security level will determine how the command is handled when execution is requested.
28 | 
29 | ## Consequences
30 | 
31 | ### Positive
32 | 
33 | - Provides a clear security model for command execution
34 | - Allows safe commands to be executed without friction
35 | - Creates an approval workflow for potentially dangerous commands
36 | - Completely blocks high-risk commands
37 | - Makes the security policy explicit and configurable
38 | 
39 | ### Negative
40 | 
41 | - Requires maintaining a whitelist of commands
42 | - May introduce friction for legitimate use cases of commands requiring approval
43 | - Initial categorization may not be perfect and could require adjustment
44 | 
45 | ## Implementation
46 | 
47 | The security levels will be implemented as an enum in the `CommandService` class:
48 | 
49 | ```typescript
50 | export enum CommandSecurityLevel {
51 |   SAFE = 'safe',
52 |   REQUIRES_APPROVAL = 'requires_approval',
53 |   FORBIDDEN = 'forbidden'
54 | }
55 | ```
56 | 
57 | Commands will be stored in a whitelist with their security level:
58 | 
59 | ```typescript
60 | export interface CommandWhitelistEntry {
61 |   command: string;
62 |   securityLevel: CommandSecurityLevel;
63 |   allowedArgs?: Array<string | RegExp>;
64 |   description?: string;
65 | }
66 | ```
67 | 
68 | When a command is executed, its security level will determine the behavior:
69 | - Safe commands are executed immediately
70 | - Commands requiring approval are queued for explicit approval
71 | - Forbidden commands are rejected with an error
```

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

```typescript
  1 | import * as fs from 'fs';
  2 | import * as path from 'path';
  3 | 
  4 | /**
  5 |  * Simple logging utility that writes to a file
  6 |  */
  7 | export class Logger {
  8 |   private logFile: string;
  9 |   private enabled: boolean;
 10 |   private fileStream: fs.WriteStream | null = null;
 11 | 
 12 |   /**
 13 |    * Create a new logger
 14 |    * @param logFile Path to the log file
 15 |    * @param enabled Whether logging is enabled
 16 |    */
 17 |   constructor(logFile: string, enabled = true) {
 18 |     this.logFile = logFile;
 19 |     this.enabled = enabled;
 20 |     
 21 |     if (this.enabled) {
 22 |       // Create the directory if it doesn't exist
 23 |       const logDir = path.dirname(this.logFile);
 24 |       console.error(`Creating log directory: ${logDir}`);
 25 |       try {
 26 |         if (!fs.existsSync(logDir)) {
 27 |           fs.mkdirSync(logDir, { recursive: true });
 28 |         }
 29 |       } catch (error) {
 30 |         console.error(`Error creating log directory: ${error}`);
 31 |         // Fall back to a directory we know exists
 32 |         this.logFile = './super-shell-mcp.log';
 33 |         console.error(`Falling back to log file: ${this.logFile}`);
 34 |       }
 35 |       
 36 |       // Create or truncate the log file
 37 |       this.fileStream = fs.createWriteStream(this.logFile, { flags: 'w' });
 38 |       
 39 |       // Write a header to the log file
 40 |       this.log('INFO', `Logging started at ${new Date().toISOString()}`);
 41 |     }
 42 |   }
 43 | 
 44 |   /**
 45 |    * Log a message
 46 |    * @param level Log level (INFO, DEBUG, ERROR, etc.)
 47 |    * @param message Message to log
 48 |    */
 49 |   public log(level: string, message: string): void {
 50 |     if (!this.enabled || !this.fileStream) {
 51 |       return;
 52 |     }
 53 |     
 54 |     const timestamp = new Date().toISOString();
 55 |     const logMessage = `[${timestamp}] [${level}] ${message}\n`;
 56 |     
 57 |     this.fileStream.write(logMessage);
 58 |   }
 59 | 
 60 |   /**
 61 |    * Log an info message
 62 |    * @param message Message to log
 63 |    */
 64 |   public info(message: string): void {
 65 |     this.log('INFO', message);
 66 |   }
 67 | 
 68 |   /**
 69 |    * Log a debug message
 70 |    * @param message Message to log
 71 |    */
 72 |   public debug(message: string): void {
 73 |     this.log('DEBUG', message);
 74 |   }
 75 | 
 76 |   /**
 77 |    * Log an error message
 78 |    * @param message Message to log
 79 |    */
 80 |   public error(message: string): void {
 81 |     this.log('ERROR', message);
 82 |   }
 83 | 
 84 |   /**
 85 |    * Close the logger
 86 |    */
 87 |   public close(): void {
 88 |     if (this.fileStream) {
 89 |       this.fileStream.end();
 90 |       this.fileStream = null;
 91 |     }
 92 |   }
 93 | }
 94 | 
 95 | // Create a singleton logger instance
 96 | let loggerInstance: Logger | null = null;
 97 | 
 98 | /**
 99 |  * Get the logger instance
100 |  * @param logFile Path to the log file
101 |  * @param enabled Whether logging is enabled
102 |  * @returns Logger instance
103 |  */
104 | export function getLogger(logFile?: string, enabled?: boolean): Logger {
105 |   if (!loggerInstance && logFile) {
106 |     loggerInstance = new Logger(logFile, enabled);
107 |   }
108 |   
109 |   if (!loggerInstance) {
110 |     throw new Error('Logger not initialized');
111 |   }
112 |   
113 |   return loggerInstance;
114 | }
```

--------------------------------------------------------------------------------
/docs/adr/002-mcp-for-shell-commands.md:
--------------------------------------------------------------------------------

```markdown
 1 | # ADR 002: Using MCP for Shell Command Execution
 2 | 
 3 | ## Status
 4 | 
 5 | Accepted
 6 | 
 7 | ## Context
 8 | 
 9 | There are several ways to provide shell command execution capabilities to AI assistants:
10 | 
11 | 1. Custom API endpoints
12 | 2. Direct integration with specific AI platforms
13 | 3. Standardized protocols like MCP (Model Context Protocol)
14 | 
15 | Each approach has different tradeoffs in terms of flexibility, security, and integration complexity. We need to decide on the most appropriate approach for our shell command execution service.
16 | 
17 | ## Decision
18 | 
19 | We will implement shell command execution as an MCP server for the following reasons:
20 | 
21 | 1. **Standardization**: MCP is an emerging standard for AI tool integration, supported by major AI platforms like Anthropic's Claude.
22 | 
23 | 2. **Discoverability**: MCP provides built-in tool discovery, allowing AI assistants to automatically learn about available commands and their parameters.
24 | 
25 | 3. **Security**: MCP's structured approach allows for clear security boundaries and validation of inputs.
26 | 
27 | 4. **Flexibility**: MCP servers can be used with any MCP-compatible client, not just specific AI platforms.
28 | 
29 | 5. **Future-proofing**: As more AI platforms adopt MCP, our implementation will be compatible without changes.
30 | 
31 | ## Consequences
32 | 
33 | ### Positive
34 | 
35 | - Works with any MCP-compatible client (Claude Desktop, etc.)
36 | - Provides structured tool definitions with clear parameter schemas
37 | - Enables dynamic tool discovery
38 | - Follows an emerging industry standard
39 | - Separates concerns between command execution and AI integration
40 | 
41 | ### Negative
42 | 
43 | - MCP is still an evolving standard
44 | - Requires understanding of MCP concepts and implementation details
45 | - May have more overhead than a direct, custom integration
46 | 
47 | ## Implementation
48 | 
49 | We will implement the MCP server using the official TypeScript SDK:
50 | 
51 | ```typescript
52 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
53 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
54 | ```
55 | 
56 | The server will expose tools for command execution and whitelist management:
57 | 
58 | ```typescript
59 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
60 |   tools: [
61 |     {
62 |       name: 'execute_command',
63 |       description: 'Execute a shell command on macOS',
64 |       inputSchema: {
65 |         type: 'object',
66 |         properties: {
67 |           command: {
68 |             type: 'string',
69 |             description: 'The command to execute',
70 |           },
71 |           args: {
72 |             type: 'array',
73 |             items: {
74 |               type: 'string',
75 |             },
76 |             description: 'Command arguments',
77 |           },
78 |         },
79 |         required: ['command'],
80 |       },
81 |     },
82 |     // Additional tools...
83 |   ],
84 | }));
85 | ```
86 | 
87 | The server will use stdio transport for compatibility with Claude Desktop and other MCP clients:
88 | 
89 | ```typescript
90 | const transport = new StdioServerTransport();
91 | await this.server.connect(transport);
```

--------------------------------------------------------------------------------
/docs/adr/003-command-approval-workflow.md:
--------------------------------------------------------------------------------

```markdown
 1 | # ADR 003: Command Approval Workflow
 2 | 
 3 | ## Status
 4 | 
 5 | Accepted
 6 | 
 7 | ## Context
 8 | 
 9 | When executing shell commands, there's a middle ground between completely safe commands and forbidden commands. Some commands can modify the system in potentially harmful ways but are still necessary for legitimate use cases. We need a mechanism to handle these commands safely.
10 | 
11 | Options considered:
12 | 1. Reject all potentially dangerous commands
13 | 2. Allow all commands with appropriate warnings
14 | 3. Implement an approval workflow for commands that require additional verification
15 | 
16 | ## Decision
17 | 
18 | We will implement an approval workflow for commands that are potentially dangerous but still necessary. This workflow will:
19 | 
20 | 1. Queue commands marked as requiring approval
21 | 2. Provide tools to list pending commands
22 | 3. Allow explicit approval or denial of pending commands
23 | 4. Execute approved commands and reject denied commands
24 | 
25 | This approach balances security with usability by allowing potentially dangerous commands to be executed after explicit approval.
26 | 
27 | ## Consequences
28 | 
29 | ### Positive
30 | 
31 | - Provides a middle ground between allowing and forbidding commands
32 | - Creates an audit trail of command approvals
33 | - Allows for human judgment in borderline cases
34 | - Enables safe use of necessary system-modifying commands
35 | - Prevents accidental execution of dangerous commands
36 | 
37 | ### Negative
38 | 
39 | - Introduces asynchronous workflow for command execution
40 | - Requires additional user interaction for approval
41 | - May create confusion if approvals are delayed or forgotten
42 | 
43 | ## Implementation
44 | 
45 | The approval workflow will be implemented using a queue of pending commands:
46 | 
47 | ```typescript
48 | interface PendingCommand {
49 |   id: string;
50 |   command: string;
51 |   args: string[];
52 |   requestedAt: Date;
53 |   requestedBy?: string;
54 |   resolve: (value: CommandResult) => void;
55 |   reject: (reason: Error) => void;
56 | }
57 | ```
58 | 
59 | When a command requiring approval is executed, it will be added to the queue:
60 | 
61 | ```typescript
62 | private queueCommandForApproval(
63 |   command: string,
64 |   args: string[] = [],
65 |   requestedBy?: string
66 | ): Promise<CommandResult> {
67 |   return new Promise((resolve, reject) => {
68 |     const id = randomUUID();
69 |     const pendingCommand: PendingCommand = {
70 |       id,
71 |       command,
72 |       args,
73 |       requestedAt: new Date(),
74 |       requestedBy,
75 |       resolve,
76 |       reject
77 |     };
78 | 
79 |     this.pendingCommands.set(id, pendingCommand);
80 |     this.emit('command:pending', pendingCommand);
81 |   });
82 | }
83 | ```
84 | 
85 | The MCP server will expose tools to list, approve, and deny pending commands:
86 | 
87 | ```typescript
88 | // Get pending commands
89 | const pendingCommands = await client.callTool('get_pending_commands', {});
90 | 
91 | // Approve a command
92 | await client.callTool('approve_command', { commandId });
93 | 
94 | // Deny a command
95 | await client.callTool('deny_command', { commandId, reason: 'Not allowed' });
96 | ```
97 | 
98 | This workflow ensures that potentially dangerous commands are only executed after explicit approval, providing an additional layer of security.
```

--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------

```javascript
  1 | import js from '@eslint/js';
  2 | import tsEslint from '@typescript-eslint/eslint-plugin';
  3 | import tsParser from '@typescript-eslint/parser';
  4 | 
  5 | export default [
  6 |   js.configs.recommended,
  7 |   {
  8 |     files: ['**/*.js'],
  9 |     languageOptions: {
 10 |       ecmaVersion: 2022,
 11 |       sourceType: 'module',
 12 |       globals: {
 13 |         console: 'readonly',
 14 |         process: 'readonly',
 15 |         Buffer: 'readonly',
 16 |         __dirname: 'readonly',
 17 |         __filename: 'readonly',
 18 |         global: 'readonly',
 19 |         module: 'readonly',
 20 |         require: 'readonly',
 21 |         exports: 'readonly',
 22 |         setTimeout: 'readonly',
 23 |         clearTimeout: 'readonly',
 24 |         setInterval: 'readonly',
 25 |         clearInterval: 'readonly',
 26 |         setImmediate: 'readonly',
 27 |         clearImmediate: 'readonly'
 28 |       }
 29 |     },
 30 |     rules: {
 31 |       'no-unused-vars': 'warn',
 32 |       'no-console': 'off',
 33 |       'prefer-const': 'error',
 34 |       'no-var': 'error'
 35 |     }
 36 |   },
 37 |   {
 38 |     files: ['**/*.ts'],
 39 |     languageOptions: {
 40 |       parser: tsParser,
 41 |       ecmaVersion: 2022,
 42 |       sourceType: 'module',
 43 |       globals: {
 44 |         console: 'readonly',
 45 |         process: 'readonly',
 46 |         Buffer: 'readonly',
 47 |         __dirname: 'readonly',
 48 |         __filename: 'readonly',
 49 |         global: 'readonly',
 50 |         module: 'readonly',
 51 |         require: 'readonly',
 52 |         exports: 'readonly',
 53 |         setTimeout: 'readonly',
 54 |         clearTimeout: 'readonly',
 55 |         setInterval: 'readonly',
 56 |         clearInterval: 'readonly',
 57 |         setImmediate: 'readonly',
 58 |         clearImmediate: 'readonly'
 59 |       }
 60 |     },
 61 |     plugins: {
 62 |       '@typescript-eslint': tsEslint
 63 |     },
 64 |     rules: {
 65 |       'no-unused-vars': 'off',
 66 |       '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
 67 |       'no-console': 'off',
 68 |       'prefer-const': 'error',
 69 |       'no-var': 'error'
 70 |     }
 71 |   },
 72 |   {
 73 |     files: ['**/*.cjs'],
 74 |     languageOptions: {
 75 |       ecmaVersion: 2022,
 76 |       sourceType: 'commonjs',
 77 |       globals: {
 78 |         console: 'readonly',
 79 |         process: 'readonly',
 80 |         Buffer: 'readonly',
 81 |         __dirname: 'readonly',
 82 |         __filename: 'readonly',
 83 |         global: 'readonly',
 84 |         module: 'readonly',
 85 |         require: 'readonly',
 86 |         exports: 'readonly',
 87 |         setTimeout: 'readonly',
 88 |         clearTimeout: 'readonly',
 89 |         setInterval: 'readonly',
 90 |         clearInterval: 'readonly',
 91 |         setImmediate: 'readonly',
 92 |         clearImmediate: 'readonly'
 93 |       }
 94 |     },
 95 |     rules: {
 96 |       'no-unused-vars': 'warn',
 97 |       'no-console': 'off',
 98 |       'prefer-const': 'error',
 99 |       'no-var': 'error'
100 |     }
101 |   },
102 |   {
103 |     files: ['**/*.test.js', '**/*.test.ts', '**/tests/**/*.js', '**/tests/**/*.ts'],
104 |     languageOptions: {
105 |       globals: {
106 |         describe: 'readonly',
107 |         test: 'readonly',
108 |         it: 'readonly',
109 |         expect: 'readonly',
110 |         beforeAll: 'readonly',
111 |         afterAll: 'readonly',
112 |         beforeEach: 'readonly',
113 |         afterEach: 'readonly',
114 |         jest: 'readonly'
115 |       }
116 |     }
117 |   },
118 |   {
119 |     ignores: ['build/**', 'node_modules/**', '*.config.js', '*.config.cjs']
120 |   }
121 | ];
```

--------------------------------------------------------------------------------
/src/utils/platform-utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as os from 'os';
  2 | import * as path from 'path';
  3 | import * as fs from 'fs';
  4 | 
  5 | /**
  6 |  * Supported platform types
  7 |  */
  8 | export enum PlatformType {
  9 |   WINDOWS = 'windows',
 10 |   MACOS = 'macos',
 11 |   LINUX = 'linux',
 12 |   UNKNOWN = 'unknown'
 13 | }
 14 | 
 15 | /**
 16 |  * Detect the current platform
 17 |  * @returns The detected platform type
 18 |  */
 19 | export function detectPlatform(): PlatformType {
 20 |   const platform = process.platform;
 21 |   
 22 |   if (platform === 'win32') return PlatformType.WINDOWS;
 23 |   if (platform === 'darwin') return PlatformType.MACOS;
 24 |   if (platform === 'linux') return PlatformType.LINUX;
 25 |   
 26 |   return PlatformType.UNKNOWN;
 27 | }
 28 | 
 29 | /**
 30 |  * Get the default shell for the current platform
 31 |  * @returns Path to the default shell
 32 |  */
 33 | export function getDefaultShell(): string {
 34 |   const platform = detectPlatform();
 35 |   
 36 |   switch (platform) {
 37 |     case PlatformType.WINDOWS:
 38 |       return process.env.COMSPEC || 'cmd.exe';
 39 |     case PlatformType.MACOS:
 40 |       return '/bin/zsh';
 41 |     case PlatformType.LINUX:
 42 |       return process.env.SHELL || '/bin/bash';
 43 |     default:
 44 |       return process.env.SHELL || '/bin/sh';
 45 |   }
 46 | }
 47 | 
 48 | /**
 49 |  * Validate if a shell path exists and is executable
 50 |  * @param shellPath Path to the shell
 51 |  * @returns True if the shell is valid
 52 |  */
 53 | export function validateShellPath(shellPath: string): boolean {
 54 |   try {
 55 |     return fs.existsSync(shellPath) && fs.statSync(shellPath).isFile();
 56 |   } catch (error) {
 57 |     return false;
 58 |   }
 59 | }
 60 | 
 61 | /**
 62 |  * Get shell suggestions for each platform
 63 |  * @returns Record of platform types to array of suggested shells
 64 |  */
 65 | export function getShellSuggestions(): Record<PlatformType, string[]> {
 66 |   return {
 67 |     [PlatformType.WINDOWS]: ['cmd.exe', 'powershell.exe', 'pwsh.exe'],
 68 |     [PlatformType.MACOS]: ['/bin/zsh', '/bin/bash', '/bin/sh'],
 69 |     [PlatformType.LINUX]: ['/bin/bash', '/bin/sh', '/bin/zsh'],
 70 |     [PlatformType.UNKNOWN]: ['/bin/sh']
 71 |   };
 72 | }
 73 | 
 74 | /**
 75 |  * Get common locations for shells on the current platform
 76 |  * @returns Array of common shell locations
 77 |  */
 78 | export function getCommonShellLocations(): string[] {
 79 |   const platform = detectPlatform();
 80 |   
 81 |   switch (platform) {
 82 |     case PlatformType.WINDOWS:
 83 |       return [
 84 |         process.env.COMSPEC || 'C:\\Windows\\System32\\cmd.exe',
 85 |         'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
 86 |         'C:\\Program Files\\PowerShell\\7\\pwsh.exe'
 87 |       ];
 88 |     case PlatformType.MACOS:
 89 |       return ['/bin/zsh', '/bin/bash', '/bin/sh'];
 90 |     case PlatformType.LINUX:
 91 |       return ['/bin/bash', '/bin/sh', '/usr/bin/bash', '/usr/bin/zsh'];
 92 |     default:
 93 |       return ['/bin/sh'];
 94 |   }
 95 | }
 96 | 
 97 | /**
 98 |  * Get helpful message for shell configuration
 99 |  * @returns A helpful message with shell configuration guidance
100 |  */
101 | export function getShellConfigurationHelp(): string {
102 |   const platform = detectPlatform();
103 |   const suggestions = getShellSuggestions()[platform];
104 |   const locations = getCommonShellLocations();
105 |   
106 |   let message = 'Shell Configuration Help:\n\n';
107 |   
108 |   message += `Detected platform: ${platform}\n\n`;
109 |   message += 'Suggested shells for this platform:\n';
110 |   suggestions.forEach(shell => {
111 |     message += `- ${shell}\n`;
112 |   });
113 |   
114 |   message += '\nCommon shell locations on this platform:\n';
115 |   locations.forEach(location => {
116 |     message += `- ${location}\n`;
117 |   });
118 |   
119 |   message += '\nTo configure a custom shell, provide the full path to the shell executable.';
120 |   
121 |   return message;
122 | }
123 | 
```

--------------------------------------------------------------------------------
/examples/client-example.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | import { McpClient } from '@modelcontextprotocol/sdk/client/index.js';
  3 | import { ChildProcessClientTransport } from '@modelcontextprotocol/sdk/client/child-process.js';
  4 | 
  5 | /**
  6 |  * Example client for the Mac Shell MCP Server
  7 |  * This demonstrates how to connect to the server and use its tools
  8 |  */
  9 | async function main() {
 10 |   // Create a client transport that connects to the server
 11 |   const transport = new ChildProcessClientTransport({
 12 |     command: 'node',
 13 |     args: ['../build/index.js'],
 14 |   });
 15 | 
 16 |   // Create the MCP client
 17 |   const client = new McpClient();
 18 |   
 19 |   try {
 20 |     // Connect to the server
 21 |     await client.connect(transport);
 22 |     console.log('Connected to Mac Shell MCP Server');
 23 | 
 24 |     // List available tools
 25 |     const tools = await client.listTools();
 26 |     console.log('Available tools:');
 27 |     tools.forEach(tool => {
 28 |       console.log(`- ${tool.name}: ${tool.description}`);
 29 |     });
 30 | 
 31 |     // Get the whitelist
 32 |     console.log('\nWhitelisted commands:');
 33 |     const whitelist = await client.callTool('get_whitelist', {});
 34 |     console.log(whitelist.content[0].text);
 35 | 
 36 |     // Execute a safe command
 37 |     console.log('\nExecuting a safe command (ls -la):');
 38 |     const lsResult = await client.callTool('execute_command', {
 39 |       command: 'ls',
 40 |       args: ['-la'],
 41 |     });
 42 |     console.log(lsResult.content[0].text);
 43 | 
 44 |     // Add a command to the whitelist
 45 |     console.log('\nAdding a command to the whitelist:');
 46 |     const addResult = await client.callTool('add_to_whitelist', {
 47 |       command: 'node',
 48 |       securityLevel: 'safe',
 49 |       description: 'Execute Node.js scripts',
 50 |     });
 51 |     console.log(addResult.content[0].text);
 52 | 
 53 |     // Try executing a command that requires approval
 54 |     console.log('\nExecuting a command that requires approval (mkdir test-dir):');
 55 |     try {
 56 |       const mkdirResult = await client.callTool('execute_command', {
 57 |         command: 'mkdir',
 58 |         args: ['test-dir'],
 59 |       });
 60 |       console.log(mkdirResult.content[0].text);
 61 |     } catch (error) {
 62 |       console.log('Command requires approval. Getting pending commands...');
 63 |       
 64 |       // Get pending commands
 65 |       const pendingCommands = await client.callTool('get_pending_commands', {});
 66 |       const pendingCommandsObj = JSON.parse(pendingCommands.content[0].text);
 67 |       
 68 |       if (pendingCommandsObj.length > 0) {
 69 |         const commandId = pendingCommandsObj[0].id;
 70 |         console.log(`Approving command with ID: ${commandId}`);
 71 |         
 72 |         // Approve the command
 73 |         const approveResult = await client.callTool('approve_command', {
 74 |           commandId,
 75 |         });
 76 |         console.log(approveResult.content[0].text);
 77 |       }
 78 |     }
 79 | 
 80 |     // Clean up - remove the test directory
 81 |     console.log('\nCleaning up:');
 82 |     try {
 83 |       // This will fail because 'rm' is forbidden
 84 |       const rmResult = await client.callTool('execute_command', {
 85 |         command: 'rm',
 86 |         args: ['-rf', 'test-dir'],
 87 |       });
 88 |       console.log(rmResult.content[0].text);
 89 |     } catch (error) {
 90 |       console.log(`Clean-up failed: ${error.message}`);
 91 |       console.log('Note: This is expected because "rm" is a forbidden command');
 92 |     }
 93 | 
 94 |   } catch (error) {
 95 |     console.error('Error:', error);
 96 |   } finally {
 97 |     // Disconnect from the server
 98 |     await client.disconnect();
 99 |     console.log('\nDisconnected from Mac Shell MCP Server');
100 |   }
101 | }
102 | 
103 | main().catch(console.error);
```

--------------------------------------------------------------------------------
/docs/adr/004-cross-platform-support.md:
--------------------------------------------------------------------------------

```markdown
  1 | # ADR 004: Cross-Platform Shell Support
  2 | 
  3 | ## Status
  4 | 
  5 | Accepted
  6 | 
  7 | ## Context
  8 | 
  9 | The original Mac Shell MCP server was designed specifically for macOS with ZSH shell. However, there's a need to support multiple platforms (Windows, macOS, Linux) and various shells (Bash, ZSH, PowerShell, CMD, etc.) to make the tool more widely usable.
 10 | 
 11 | Key limitations in the original implementation:
 12 | 
 13 | 1. **Shell Path Hardcoding**: The server was hardcoded to use `/bin/zsh` as the default shell
 14 | 2. **Command Set Assumptions**: The default whitelist included macOS/Unix commands that don't exist natively on Windows
 15 | 3. **Path Handling**: Command validation extracted the base command by splitting on '/' which doesn't work for Windows backslash paths
 16 | 4. **Naming and Documentation**: The server was explicitly named "mac-shell-mcp" and documented for macOS
 17 | 
 18 | ## Decision
 19 | 
 20 | We will refactor the server to be platform-agnostic with the following changes:
 21 | 
 22 | 1. **Platform Detection**: Implement platform detection using `process.platform` to identify the current operating system
 23 | 2. **Shell Selection**: Select appropriate default shell based on platform and allow shell path to be configurable
 24 | 3. **Path Normalization**: Use Node.js `path` module for cross-platform path handling
 25 | 4. **Platform-Specific Command Whitelists**: Implement separate command whitelists for each supported platform
 26 | 5. **Rename and Rebrand**: Rename to "super-shell-mcp" and update documentation to reflect cross-platform support
 27 | 
 28 | ## Consequences
 29 | 
 30 | ### Positive
 31 | 
 32 | - Works across Windows, macOS, and Linux
 33 | - Supports various shells based on user preference
 34 | - Maintains the same security model across platforms
 35 | - Provides consistent experience regardless of platform
 36 | - Increases the potential user base by supporting multiple platforms
 37 | 
 38 | ### Negative
 39 | 
 40 | - Increased complexity in command handling
 41 | - Need to maintain separate command whitelists for each platform
 42 | - Some commands may behave differently across platforms
 43 | - Testing becomes more complex, requiring validation on multiple platforms
 44 | 
 45 | ## Implementation
 46 | 
 47 | The implementation uses a platform detection utility:
 48 | 
 49 | ```typescript
 50 | export function detectPlatform(): PlatformType {
 51 |   const platform = process.platform;
 52 |   
 53 |   if (platform === 'win32') return PlatformType.WINDOWS;
 54 |   if (platform === 'darwin') return PlatformType.MACOS;
 55 |   if (platform === 'linux') return PlatformType.LINUX;
 56 |   
 57 |   return PlatformType.UNKNOWN;
 58 | }
 59 | ```
 60 | 
 61 | Platform-specific shell detection:
 62 | 
 63 | ```typescript
 64 | export function getDefaultShell(): string {
 65 |   const platform = detectPlatform();
 66 |   
 67 |   switch (platform) {
 68 |     case PlatformType.WINDOWS:
 69 |       return process.env.COMSPEC || 'cmd.exe';
 70 |     case PlatformType.MACOS:
 71 |       return '/bin/zsh';
 72 |     case PlatformType.LINUX:
 73 |       return process.env.SHELL || '/bin/bash';
 74 |     default:
 75 |       return process.env.SHELL || '/bin/sh';
 76 |   }
 77 | }
 78 | ```
 79 | 
 80 | Platform-specific command whitelists:
 81 | 
 82 | ```typescript
 83 | private initializeDefaultWhitelist(): void {
 84 |   const platformCommands = getPlatformSpecificCommands();
 85 |   
 86 |   platformCommands.forEach(entry => {
 87 |     this.whitelist.set(entry.command, entry);
 88 |   });
 89 | }
 90 | ```
 91 | 
 92 | Cross-platform path handling:
 93 | 
 94 | ```typescript
 95 | private validateCommand(command: string, args: string[]): CommandSecurityLevel | null {
 96 |   // Extract the base command (without path) using path.basename
 97 |   const baseCommand = path.basename(command);
 98 |   
 99 |   // Check if the command is in the whitelist
100 |   const entry = this.whitelist.get(baseCommand);
101 |   if (!entry) {
102 |     return null;
103 |   }
104 |   
105 |   // Rest of validation...
106 | }
```

--------------------------------------------------------------------------------
/tests/command-service.platform.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | const { CommandService, CommandSecurityLevel } = require('../build/services/command-service.js');
  2 | const { detectPlatform, PlatformType } = require('../build/utils/platform-utils.js');
  3 | 
  4 | describe('CommandService Platform Tests', () => {
  5 |   let commandService;
  6 |   const currentPlatform = detectPlatform();
  7 | 
  8 |   beforeEach(() => {
  9 |     // Create a new CommandService instance for each test with auto-detected shell
 10 |     commandService = new CommandService();
 11 |   });
 12 | 
 13 |   test('should initialize with platform-specific whitelist', () => {
 14 |     const whitelist = commandService.getWhitelist();
 15 |     expect(whitelist).toBeDefined();
 16 |     expect(whitelist.length).toBeGreaterThan(0);
 17 |     
 18 |     // Check for common command across all platforms
 19 |     const echoCommand = whitelist.find(entry => entry.command === 'echo');
 20 |     expect(echoCommand).toBeDefined();
 21 |     expect(echoCommand.securityLevel).toBe(CommandSecurityLevel.SAFE);
 22 |     
 23 |     // Platform-specific command checks
 24 |     if (currentPlatform === PlatformType.WINDOWS) {
 25 |       // Windows-specific commands
 26 |       const dirCommand = whitelist.find(entry => entry.command === 'dir');
 27 |       expect(dirCommand).toBeDefined();
 28 |       expect(dirCommand.securityLevel).toBe(CommandSecurityLevel.SAFE);
 29 |       
 30 |       const delCommand = whitelist.find(entry => entry.command === 'del');
 31 |       expect(delCommand).toBeDefined();
 32 |       expect(delCommand.securityLevel).toBe(CommandSecurityLevel.FORBIDDEN);
 33 |     } else {
 34 |       // Unix-like platforms (macOS, Linux)
 35 |       const lsCommand = whitelist.find(entry => entry.command === 'ls');
 36 |       expect(lsCommand).toBeDefined();
 37 |       expect(lsCommand.securityLevel).toBe(CommandSecurityLevel.SAFE);
 38 |       
 39 |       const rmCommand = whitelist.find(entry => entry.command === 'rm');
 40 |       expect(rmCommand).toBeDefined();
 41 |       expect(rmCommand.securityLevel).toBe(CommandSecurityLevel.FORBIDDEN);
 42 |     }
 43 |   });
 44 | 
 45 |   test('should execute platform-specific safe command', async () => {
 46 |     // Choose a command based on platform
 47 |     const command = currentPlatform === PlatformType.WINDOWS ? 'echo' : 'echo';
 48 |     const args = ['test'];
 49 |     
 50 |     const result = await commandService.executeCommand(command, args);
 51 |     
 52 |     expect(result).toBeDefined();
 53 |     expect(result.stdout.trim()).toBe('test');
 54 |   });
 55 | 
 56 |   test('should reject platform-specific forbidden command', async () => {
 57 |     // Choose a forbidden command based on platform
 58 |     const command = currentPlatform === PlatformType.WINDOWS ? 'del' : 'rm';
 59 |     const args = currentPlatform === PlatformType.WINDOWS ? ['test.txt'] : ['-rf', 'test'];
 60 |     
 61 |     await expect(commandService.executeCommand(command, args)).rejects.toThrow();
 62 |   });
 63 | 
 64 |   test('should queue platform-specific command requiring approval', async () => {
 65 |     // Set up event listener to capture pending command
 66 |     let pendingCommandId = null;
 67 |     commandService.on('command:pending', (pendingCommand) => {
 68 |       pendingCommandId = pendingCommand.id;
 69 |     });
 70 |     
 71 |     // Choose a command requiring approval based on platform
 72 |     // Use a command that doesn't actually create anything to avoid test failures
 73 |     const command = currentPlatform === PlatformType.WINDOWS ? 'copy' : 'cp';
 74 |     const args = ['nonexistent-file', 'nonexistent-copy'];
 75 |     
 76 |     // Execute a command that requires approval
 77 |     const executePromise = commandService.executeCommand(command, args);
 78 |     
 79 |     // Wait a bit for the event to fire
 80 |     await new Promise(resolve => setTimeout(resolve, 100));
 81 |     
 82 |     // Check if we got a pending command
 83 |     expect(pendingCommandId).not.toBeNull();
 84 |     
 85 |     // Get pending commands
 86 |     const pendingCommands = commandService.getPendingCommands();
 87 |     expect(pendingCommands.length).toBe(1);
 88 |     expect(pendingCommands[0].id).toBe(pendingCommandId);
 89 |     
 90 |     // Approve the command
 91 |     const approvePromise = commandService.approveCommand(pendingCommandId);
 92 |     
 93 |     try {
 94 |       // Wait for both promises to resolve
 95 |       await Promise.all([executePromise, approvePromise]);
 96 |     } catch (error) {
 97 |       // Expect an error since we're trying to copy a non-existent file
 98 |       // This is expected and we can ignore it
 99 |     }
100 |     
101 |     // Check that there are no more pending commands
102 |     expect(commandService.getPendingCommands().length).toBe(0);
103 |   });
104 | 
105 |   test('should deny platform-specific command requiring approval', async () => {
106 |     // Set up event listener to capture pending command
107 |     let pendingCommandId = null;
108 |     commandService.on('command:pending', (pendingCommand) => {
109 |       pendingCommandId = pendingCommand.id;
110 |     });
111 |     
112 |     // Choose a command requiring approval based on platform
113 |     const command = currentPlatform === PlatformType.WINDOWS ? 'mkdir' : 'mkdir';
114 |     const args = ['test-dir'];
115 |     
116 |     // Execute a command that requires approval
117 |     const executePromise = commandService.executeCommand(command, args);
118 |     
119 |     // Wait a bit for the event to fire
120 |     await new Promise(resolve => setTimeout(resolve, 100));
121 |     
122 |     // Check if we got a pending command
123 |     expect(pendingCommandId).not.toBeNull();
124 |     
125 |     // Deny the command
126 |     commandService.denyCommand(pendingCommandId, 'Test denial');
127 |     
128 |     // The execute promise should be rejected
129 |     await expect(executePromise).rejects.toThrow('Test denial');
130 |     
131 |     // Check that there are no more pending commands
132 |     expect(commandService.getPendingCommands().length).toBe(0);
133 |   });
134 | });
```

--------------------------------------------------------------------------------
/tests/command-service.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | const { CommandService, CommandSecurityLevel } = require('../build/services/command-service.js');
  2 | const { getDefaultShell } = require('../build/utils/platform-utils.js');
  3 | 
  4 | describe('CommandService', () => {
  5 |   let commandService;
  6 | 
  7 |   beforeEach(() => {
  8 |     // Create a new CommandService instance for each test with auto-detected shell
  9 |     commandService = new CommandService();
 10 |   });
 11 | 
 12 |   test('should initialize with default whitelist', () => {
 13 |     const whitelist = commandService.getWhitelist();
 14 |     expect(whitelist).toBeDefined();
 15 |     expect(whitelist.length).toBeGreaterThan(0);
 16 |     
 17 |     // Check if common commands are in the whitelist
 18 |     const lsCommand = whitelist.find(entry => entry.command === 'ls');
 19 |     expect(lsCommand).toBeDefined();
 20 |     expect(lsCommand.securityLevel).toBe(CommandSecurityLevel.SAFE);
 21 |     
 22 |     const rmCommand = whitelist.find(entry => entry.command === 'rm');
 23 |     expect(rmCommand).toBeDefined();
 24 |     expect(rmCommand.securityLevel).toBe(CommandSecurityLevel.FORBIDDEN);
 25 |   });
 26 | 
 27 |   test('should add command to whitelist', () => {
 28 |     const testCommand = {
 29 |       command: 'test-command',
 30 |       securityLevel: CommandSecurityLevel.SAFE,
 31 |       description: 'Test command'
 32 |     };
 33 |     
 34 |     commandService.addToWhitelist(testCommand);
 35 |     
 36 |     const whitelist = commandService.getWhitelist();
 37 |     const addedCommand = whitelist.find(entry => entry.command === 'test-command');
 38 |     
 39 |     expect(addedCommand).toBeDefined();
 40 |     expect(addedCommand.securityLevel).toBe(CommandSecurityLevel.SAFE);
 41 |     expect(addedCommand.description).toBe('Test command');
 42 |   });
 43 | 
 44 |   test('should update command security level', () => {
 45 |     // First add a command
 46 |     const testCommand = {
 47 |       command: 'test-command',
 48 |       securityLevel: CommandSecurityLevel.SAFE,
 49 |       description: 'Test command'
 50 |     };
 51 |     
 52 |     commandService.addToWhitelist(testCommand);
 53 |     
 54 |     // Then update its security level
 55 |     commandService.updateSecurityLevel('test-command', CommandSecurityLevel.REQUIRES_APPROVAL);
 56 |     
 57 |     const whitelist = commandService.getWhitelist();
 58 |     const updatedCommand = whitelist.find(entry => entry.command === 'test-command');
 59 |     
 60 |     expect(updatedCommand).toBeDefined();
 61 |     expect(updatedCommand.securityLevel).toBe(CommandSecurityLevel.REQUIRES_APPROVAL);
 62 |   });
 63 | 
 64 |   test('should remove command from whitelist', () => {
 65 |     // First add a command
 66 |     const testCommand = {
 67 |       command: 'test-command',
 68 |       securityLevel: CommandSecurityLevel.SAFE,
 69 |       description: 'Test command'
 70 |     };
 71 |     
 72 |     commandService.addToWhitelist(testCommand);
 73 |     
 74 |     // Then remove it
 75 |     commandService.removeFromWhitelist('test-command');
 76 |     
 77 |     const whitelist = commandService.getWhitelist();
 78 |     const removedCommand = whitelist.find(entry => entry.command === 'test-command');
 79 |     
 80 |     expect(removedCommand).toBeUndefined();
 81 |   });
 82 | 
 83 |   test('should execute safe command', async () => {
 84 |     // Execute a safe command (echo)
 85 |     const result = await commandService.executeCommand('echo', ['test']);
 86 |     
 87 |     expect(result).toBeDefined();
 88 |     expect(result.stdout.trim()).toBe('test');
 89 |   });
 90 | 
 91 |   test('should reject forbidden command', async () => {
 92 |     // Try to execute a forbidden command (rm)
 93 |     await expect(commandService.executeCommand('rm', ['-rf', 'test'])).rejects.toThrow();
 94 |   });
 95 | 
 96 |   test('should queue command requiring approval', async () => {
 97 |     // Set up event listener to capture pending command
 98 |     let pendingCommandId = null;
 99 |     commandService.on('command:pending', (pendingCommand) => {
100 |       pendingCommandId = pendingCommand.id;
101 |     });
102 |     
103 |     // Execute a command that requires approval
104 |     // Use a command that doesn't actually create anything to avoid test failures
105 |     const executePromise = commandService.executeCommand('cp', ['nonexistent-file', 'nonexistent-copy']);
106 |     
107 |     // Wait a bit for the event to fire
108 |     await new Promise(resolve => setTimeout(resolve, 100));
109 |     
110 |     // Check if we got a pending command
111 |     expect(pendingCommandId).not.toBeNull();
112 |     
113 |     // Get pending commands
114 |     const pendingCommands = commandService.getPendingCommands();
115 |     expect(pendingCommands.length).toBe(1);
116 |     expect(pendingCommands[0].id).toBe(pendingCommandId);
117 |     
118 |     // Approve the command
119 |     const approvePromise = commandService.approveCommand(pendingCommandId);
120 |     
121 |     try {
122 |       // Wait for both promises to resolve
123 |       await Promise.all([executePromise, approvePromise]);
124 |     } catch (error) {
125 |       // Expect an error since we're trying to copy a non-existent file
126 |       // This is expected and we can ignore it
127 |     }
128 |     
129 |     // Check that there are no more pending commands
130 |     expect(commandService.getPendingCommands().length).toBe(0);
131 |     
132 |     // Clean up
133 |     try {
134 |       await commandService.executeCommand('rmdir', ['test-dir']);
135 |     } catch (error) {
136 |       // Ignore cleanup errors
137 |     }
138 |   });
139 | 
140 |   test('should deny command requiring approval', async () => {
141 |     // Set up event listener to capture pending command
142 |     let pendingCommandId = null;
143 |     commandService.on('command:pending', (pendingCommand) => {
144 |       pendingCommandId = pendingCommand.id;
145 |     });
146 |     
147 |     // Execute a command that requires approval (mkdir)
148 |     const executePromise = commandService.executeCommand('mkdir', ['test-dir']);
149 |     
150 |     // Wait a bit for the event to fire
151 |     await new Promise(resolve => setTimeout(resolve, 100));
152 |     
153 |     // Check if we got a pending command
154 |     expect(pendingCommandId).not.toBeNull();
155 |     
156 |     // Deny the command
157 |     commandService.denyCommand(pendingCommandId, 'Test denial');
158 |     
159 |     // The execute promise should be rejected
160 |     await expect(executePromise).rejects.toThrow('Test denial');
161 |     
162 |     // Check that there are no more pending commands
163 |     expect(commandService.getPendingCommands().length).toBe(0);
164 |   });
165 | });
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Changelog
  2 | 
  3 | All notable changes to this project will be documented in this file.
  4 | 
  5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
  6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
  7 | ## [2.0.13] - 2025-03-14
  8 | 
  9 | ### Fixed
 10 |  - Updated the package.json file to include the proper github repo information
 11 | 
 12 | ## [2.0.12] - 2025-03-14
 13 | 
 14 | ### Changed
 15 | - Converted project from CommonJS to ESM module system
 16 | - Updated TypeScript configuration to use NodeNext module system
 17 | - Added ESM-compatible workaround for `__dirname` in ESM context
 18 | 
 19 | ### Fixed
 20 | - Fixed Jest test suite to work with ESM modules
 21 | - Added CommonJS-compatible mock modules for testing
 22 | - Updated module imports to use ESM syntax with .js extensions
 23 | 
 24 | ## [2.0.11] - 2025-03-14
 25 | 
 26 | ### Added
 27 | - Published package to npm at https://www.npmjs.com/package/super-shell-mcp
 28 | - Updated README.md to highlight NPX installation method as the recommended approach
 29 | - Added benefits of using NPX in documentation
 30 | - Enhanced configuration examples for easier setup
 31 | 
 32 | ### Changed
 33 | - Reorganized documentation to prioritize NPX installation method
 34 | - Simplified GitHub installation instructions
 35 | - Improved package publishing documentation
 36 | 
 37 | ## [2.0.10] - 2025-03-14
 38 | 
 39 | ### Added
 40 | - Added logs directory with .gitkeep file to ensure logs directory is tracked by Git
 41 | - Enhanced error handling for command approval process
 42 | - Improved logging for command execution and approval workflow
 43 | 
 44 | ### Fixed
 45 | - Fixed version inconsistencies across package.json, package-lock.json, and src/index.ts
 46 | - Improved documentation for logging system
 47 | - Enhanced cross-platform compatibility for log file paths
 48 | 
 49 | ## [2.0.9] - 2025-03-14
 50 | 
 51 | ### Added
 52 | - Added additional logging for command approval workflow
 53 | - Improved error handling for command execution
 54 | - Enhanced debugging capabilities with more detailed logs
 55 | 
 56 | ### Fixed
 57 | - Fixed minor issues with command approval workflow
 58 | - Improved reliability of command execution across platforms
 59 | - Enhanced error messages for better troubleshooting
 60 | 
 61 | ## [2.0.8] - 2025-03-13
 62 | 
 63 | ### Added
 64 | - Added comprehensive logging system with file-based logs
 65 | - Implemented non-blocking command approval workflow
 66 | - Added new `queueCommandForApprovalNonBlocking` method to CommandService
 67 | 
 68 | ### Fixed
 69 | - Fixed timeout issue with commands requiring approval by implementing non-blocking approval workflow
 70 | - Improved user experience by providing immediate feedback for commands requiring approval
 71 | - Enhanced error handling for commands requiring approval
 72 | - Fixed issue where pending commands would cause client timeouts
 73 | 
 74 | ## [2.0.7] - 2025-03-13
 75 | 
 76 | ### Fixed
 77 | - Fixed timeout issue when using the "Approve" button in Roo Code client
 78 | - Improved error handling in `handleApproveCommand` method to bypass Promise resolution mechanism
 79 | - Added detailed logging for command approval process to aid debugging
 80 | - Enhanced direct command execution in approval workflow to prevent timeouts
 81 | - Fixed TypeScript errors related to error handling in command execution
 82 | 
 83 | ## [2.0.6] - 2025-03-13
 84 | 
 85 | ### Fixed
 86 | - Fixed command approval workflow to prevent timeout errors
 87 | - Added immediate detection of commands requiring approval
 88 | - Improved error messages for commands requiring approval with clear instructions
 89 | - Added direct guidance to use get_pending_commands and approve_command functions
 90 | 
 91 | ## [2.0.5] - 2025-03-13
 92 | 
 93 | ### Added
 94 | - Improved command approval workflow with timeout detection
 95 | - Added guidance for AI assistants when command approval times out
 96 | - Enhanced error messages for commands requiring approval
 97 | 
 98 | ### Fixed
 99 | - Module compatibility issues between ES Modules and CommonJS in tests
100 | - Updated TypeScript configuration to use CommonJS module system for better test compatibility
101 | - Removed unnecessary CommonJS compatibility code from source files
102 | - Changed package.json "type" from "module" to "commonjs" for consistent module system
103 | 
104 | ## [2.0.4] - 2025-03-13
105 | 
106 | ### Fixed
107 | - Module compatibility issues between ES Modules and CommonJS in tests
108 | - Updated TypeScript configuration to use CommonJS module system for better test compatibility
109 | - Removed unnecessary CommonJS compatibility code from source files
110 | - Changed package.json "type" from "module" to "commonjs" for consistent module system
111 | 
112 | ## [2.0.3] - 2025-03-12
113 | 
114 | ### Added
115 | - NPX best practices documentation in README.md
116 | - Improved package.json configuration for NPX compatibility
117 | 
118 | ### Changed
119 | - Updated TypeScript configuration to use NodeNext module system for better ES Modules support
120 | - Added prepare script to ensure compilation happens on install
121 | - Added files field to package.json to specify which files to include when publishing
122 | - Simplified build script to use chmod directly instead of custom script
123 | 
124 | ## [2.0.2] - 2025-03-12
125 | 
126 | ### Fixed
127 | - Test suite compatibility with cross-platform environments
128 | - Module system compatibility between ESM and CommonJS
129 | - Fixed platform-specific test cases to work on all operating systems
130 | - Updated TypeScript configuration for better CommonJS compatibility
131 | - Improved test reliability by using non-filesystem-modifying commands
132 | 
133 | ## [2.0.1] - 2025-03-12
134 | 
135 | ### Added
136 | - Platform-aware test suite that adapts to the current operating system
137 | - Cross-platform build script that works on Windows, macOS, and Linux
138 | - Enhanced platform-specific documentation with configuration examples
139 | - Troubleshooting guide for common cross-platform issues
140 | - Detailed shell path examples for each supported platform
141 | 
142 | ### Fixed
143 | - Build script compatibility with Windows (removed Unix-specific chmod)
144 | - Test suite compatibility with Windows command sets
145 | - Path handling in shell validation for Windows paths
146 | 
147 | ## [2.0.0] - 2025-03-12
148 | 
149 | ### Added
150 | - Cross-platform support for Windows, macOS, and Linux
151 | - Platform detection using `process.platform`
152 | - Auto-detection of appropriate shell based on platform
153 | - Platform-specific command whitelists
154 | - New `get_platform_info` tool to retrieve platform and shell information
155 | - Support for Windows shells (cmd.exe, PowerShell)
156 | - Support for Linux shells (bash, sh)
157 | - New ADR for cross-platform support
158 | 
159 | ### Changed
160 | - Renamed from "mac-shell-mcp" to "super-shell-mcp"
161 | - Updated path handling to use Node.js path module for cross-platform compatibility
162 | - Modified command validation to work with Windows paths
163 | - Updated documentation to reflect cross-platform support
164 | - Refactored code to be platform-agnostic
165 | - Made shell configurable with auto-detection as fallback
166 | 
167 | ## [1.0.3] - 2025-03-12
168 | 
169 | ### Fixed
170 | 
171 | - Improved documentation for Claude Desktop configuration which uses boolean value for `alwaysAllow`
172 | - Added separate configuration examples for Roo Code and Claude Desktop
173 | - Clarified that Roo Code uses array format while Claude Desktop uses boolean format
174 | - Added explicit note that the `alwaysAllow` parameter is processed by the MCP client, not the server
175 | 
176 | ## [1.0.2] - 2025-03-12
177 | 
178 | ### Fixed
179 | 
180 | - Fixed MCP configuration format to use an empty array `[]` for `alwaysAllow` instead of `false`
181 | - Updated all configuration examples in README.md to use the correct format
182 | - Fixed error "Invalid config: missing or invalid parameters" when adding to MCP settings
183 | 
184 | ## [1.0.1] - 2025-03-12
185 | 
186 | ### Added
187 | 
188 | - Support for using the server as an npm package with npx
189 | - Added bin field to package.json for CLI usage
190 | - Improved MCP configuration instructions for Roo Code and Claude Desktop
191 | - Added examples for using with npx directly from GitHub
192 | 
193 | ## [1.0.0] - 2025-03-12
194 | 
195 | ### Added
196 | 
197 | - Initial release of the Mac Shell MCP Server
198 | - Command execution service with ZSH shell support
199 | - Command whitelisting system with three security levels:
200 |   - Safe commands (no approval required)
201 |   - Commands requiring approval
202 |   - Forbidden commands
203 | - Pre-configured whitelist with common safe commands
204 | - Approval workflow for potentially dangerous commands
205 | - MCP tools for command execution and whitelist management:
206 |   - `execute_command`: Execute shell commands
207 |   - `get_whitelist`: Get the list of whitelisted commands
208 |   - `add_to_whitelist`: Add a command to the whitelist
209 |   - `update_security_level`: Update a command's security level
210 |   - `remove_from_whitelist`: Remove a command from the whitelist
211 |   - `get_pending_commands`: Get commands pending approval
212 |   - `approve_command`: Approve a pending command
213 |   - `deny_command`: Deny a pending command
214 | - Comprehensive test suite for the command service
215 | - Example client implementation
216 | - Documentation and configuration examples
```

--------------------------------------------------------------------------------
/src/utils/command-whitelist-utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { CommandSecurityLevel, CommandWhitelistEntry } from '../services/command-service.js';
  2 | import { PlatformType, detectPlatform } from './platform-utils.js';
  3 | 
  4 | /**
  5 |  * Get common safe commands that work across all platforms
  6 |  * @returns Array of common safe command whitelist entries
  7 |  */
  8 | export function getCommonSafeCommands(): CommandWhitelistEntry[] {
  9 |   return [
 10 |     {
 11 |       command: 'echo',
 12 |       securityLevel: CommandSecurityLevel.SAFE,
 13 |       description: 'Print text to standard output'
 14 |     }
 15 |   ];
 16 | }
 17 | 
 18 | /**
 19 |  * Get Windows-specific safe commands
 20 |  * @returns Array of Windows safe command whitelist entries
 21 |  */
 22 | export function getWindowsSafeCommands(): CommandWhitelistEntry[] {
 23 |   return [
 24 |     {
 25 |       command: 'dir',
 26 |       securityLevel: CommandSecurityLevel.SAFE,
 27 |       description: 'List directory contents'
 28 |     },
 29 |     {
 30 |       command: 'type',
 31 |       securityLevel: CommandSecurityLevel.SAFE,
 32 |       description: 'Display the contents of a text file'
 33 |     },
 34 |     {
 35 |       command: 'cd',
 36 |       securityLevel: CommandSecurityLevel.SAFE,
 37 |       description: 'Change directory'
 38 |     },
 39 |     {
 40 |       command: 'findstr',
 41 |       securityLevel: CommandSecurityLevel.SAFE,
 42 |       description: 'Search for strings in files'
 43 |     },
 44 |     {
 45 |       command: 'where',
 46 |       securityLevel: CommandSecurityLevel.SAFE,
 47 |       description: 'Locate programs'
 48 |     },
 49 |     {
 50 |       command: 'whoami',
 51 |       securityLevel: CommandSecurityLevel.SAFE,
 52 |       description: 'Display current user'
 53 |     },
 54 |     {
 55 |       command: 'hostname',
 56 |       securityLevel: CommandSecurityLevel.SAFE,
 57 |       description: 'Display computer name'
 58 |     },
 59 |     {
 60 |       command: 'ver',
 61 |       securityLevel: CommandSecurityLevel.SAFE,
 62 |       description: 'Display operating system version'
 63 |     }
 64 |   ];
 65 | }
 66 | 
 67 | /**
 68 |  * Get macOS-specific safe commands
 69 |  * @returns Array of macOS safe command whitelist entries
 70 |  */
 71 | export function getMacOSSafeCommands(): CommandWhitelistEntry[] {
 72 |   return [
 73 |     {
 74 |       command: 'ls',
 75 |       securityLevel: CommandSecurityLevel.SAFE,
 76 |       description: 'List directory contents'
 77 |     },
 78 |     {
 79 |       command: 'pwd',
 80 |       securityLevel: CommandSecurityLevel.SAFE,
 81 |       description: 'Print working directory'
 82 |     },
 83 |     {
 84 |       command: 'cat',
 85 |       securityLevel: CommandSecurityLevel.SAFE,
 86 |       description: 'Concatenate and print files'
 87 |     },
 88 |     {
 89 |       command: 'grep',
 90 |       securityLevel: CommandSecurityLevel.SAFE,
 91 |       description: 'Search for patterns in files'
 92 |     },
 93 |     {
 94 |       command: 'find',
 95 |       securityLevel: CommandSecurityLevel.SAFE,
 96 |       description: 'Find files in a directory hierarchy'
 97 |     },
 98 |     {
 99 |       command: 'cd',
100 |       securityLevel: CommandSecurityLevel.SAFE,
101 |       description: 'Change directory'
102 |     },
103 |     {
104 |       command: 'head',
105 |       securityLevel: CommandSecurityLevel.SAFE,
106 |       description: 'Output the first part of files'
107 |     },
108 |     {
109 |       command: 'tail',
110 |       securityLevel: CommandSecurityLevel.SAFE,
111 |       description: 'Output the last part of files'
112 |     },
113 |     {
114 |       command: 'wc',
115 |       securityLevel: CommandSecurityLevel.SAFE,
116 |       description: 'Print newline, word, and byte counts'
117 |     }
118 |   ];
119 | }
120 | 
121 | /**
122 |  * Get Linux-specific safe commands
123 |  * @returns Array of Linux safe command whitelist entries
124 |  */
125 | export function getLinuxSafeCommands(): CommandWhitelistEntry[] {
126 |   // Linux safe commands are similar to macOS
127 |   return getMacOSSafeCommands();
128 | }
129 | 
130 | /**
131 |  * Get Windows-specific commands requiring approval
132 |  * @returns Array of Windows command whitelist entries requiring approval
133 |  */
134 | export function getWindowsApprovalCommands(): CommandWhitelistEntry[] {
135 |   return [
136 |     {
137 |       command: 'copy',
138 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
139 |       description: 'Copy files'
140 |     },
141 |     {
142 |       command: 'move',
143 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
144 |       description: 'Move files'
145 |     },
146 |     {
147 |       command: 'mkdir',
148 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
149 |       description: 'Create directories'
150 |     },
151 |     {
152 |       command: 'rmdir',
153 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
154 |       description: 'Remove directories'
155 |     },
156 |     {
157 |       command: 'rename',
158 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
159 |       description: 'Rename files'
160 |     },
161 |     {
162 |       command: 'attrib',
163 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
164 |       description: 'Change file attributes'
165 |     }
166 |   ];
167 | }
168 | 
169 | /**
170 |  * Get macOS-specific commands requiring approval
171 |  * @returns Array of macOS command whitelist entries requiring approval
172 |  */
173 | export function getMacOSApprovalCommands(): CommandWhitelistEntry[] {
174 |   return [
175 |     {
176 |       command: 'mv',
177 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
178 |       description: 'Move (rename) files'
179 |     },
180 |     {
181 |       command: 'cp',
182 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
183 |       description: 'Copy files and directories'
184 |     },
185 |     {
186 |       command: 'mkdir',
187 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
188 |       description: 'Create directories'
189 |     },
190 |     {
191 |       command: 'touch',
192 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
193 |       description: 'Change file timestamps or create empty files'
194 |     },
195 |     {
196 |       command: 'chmod',
197 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
198 |       description: 'Change file mode bits'
199 |     },
200 |     {
201 |       command: 'chown',
202 |       securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL,
203 |       description: 'Change file owner and group'
204 |     }
205 |   ];
206 | }
207 | 
208 | /**
209 |  * Get Linux-specific commands requiring approval
210 |  * @returns Array of Linux command whitelist entries requiring approval
211 |  */
212 | export function getLinuxApprovalCommands(): CommandWhitelistEntry[] {
213 |   // Linux approval commands are similar to macOS
214 |   return getMacOSApprovalCommands();
215 | }
216 | 
217 | /**
218 |  * Get Windows-specific forbidden commands
219 |  * @returns Array of Windows forbidden command whitelist entries
220 |  */
221 | export function getWindowsForbiddenCommands(): CommandWhitelistEntry[] {
222 |   return [
223 |     {
224 |       command: 'del',
225 |       securityLevel: CommandSecurityLevel.FORBIDDEN,
226 |       description: 'Delete files'
227 |     },
228 |     {
229 |       command: 'erase',
230 |       securityLevel: CommandSecurityLevel.FORBIDDEN,
231 |       description: 'Delete files'
232 |     },
233 |     {
234 |       command: 'format',
235 |       securityLevel: CommandSecurityLevel.FORBIDDEN,
236 |       description: 'Format a disk'
237 |     },
238 |     {
239 |       command: 'runas',
240 |       securityLevel: CommandSecurityLevel.FORBIDDEN,
241 |       description: 'Execute a program as another user'
242 |     }
243 |   ];
244 | }
245 | 
246 | /**
247 |  * Get macOS-specific forbidden commands
248 |  * @returns Array of macOS forbidden command whitelist entries
249 |  */
250 | export function getMacOSForbiddenCommands(): CommandWhitelistEntry[] {
251 |   return [
252 |     {
253 |       command: 'rm',
254 |       securityLevel: CommandSecurityLevel.FORBIDDEN,
255 |       description: 'Remove files or directories'
256 |     },
257 |     {
258 |       command: 'sudo',
259 |       securityLevel: CommandSecurityLevel.FORBIDDEN,
260 |       description: 'Execute a command as another user'
261 |     }
262 |   ];
263 | }
264 | 
265 | /**
266 |  * Get Linux-specific forbidden commands
267 |  * @returns Array of Linux forbidden command whitelist entries
268 |  */
269 | export function getLinuxForbiddenCommands(): CommandWhitelistEntry[] {
270 |   // Linux forbidden commands are similar to macOS
271 |   return getMacOSForbiddenCommands();
272 | }
273 | 
274 | /**
275 |  * Get platform-specific command whitelist entries
276 |  * @returns Array of command whitelist entries for the current platform
277 |  */
278 | export function getPlatformSpecificCommands(): CommandWhitelistEntry[] {
279 |   const platform = detectPlatform();
280 |   
281 |   let safeCommands: CommandWhitelistEntry[] = [];
282 |   let approvalCommands: CommandWhitelistEntry[] = [];
283 |   let forbiddenCommands: CommandWhitelistEntry[] = [];
284 |   
285 |   // Add common safe commands that work across all platforms
286 |   const commonSafeCommands = getCommonSafeCommands();
287 |   
288 |   // Add platform-specific commands
289 |   switch (platform) {
290 |     case PlatformType.WINDOWS:
291 |       safeCommands = getWindowsSafeCommands();
292 |       approvalCommands = getWindowsApprovalCommands();
293 |       forbiddenCommands = getWindowsForbiddenCommands();
294 |       break;
295 |     case PlatformType.MACOS:
296 |       safeCommands = getMacOSSafeCommands();
297 |       approvalCommands = getMacOSApprovalCommands();
298 |       forbiddenCommands = getMacOSForbiddenCommands();
299 |       break;
300 |     case PlatformType.LINUX:
301 |       safeCommands = getLinuxSafeCommands();
302 |       approvalCommands = getLinuxApprovalCommands();
303 |       forbiddenCommands = getLinuxForbiddenCommands();
304 |       break;
305 |     default:
306 |       // Use Unix-like defaults for unknown platforms
307 |       safeCommands = getLinuxSafeCommands();
308 |       approvalCommands = getLinuxApprovalCommands();
309 |       forbiddenCommands = getLinuxForbiddenCommands();
310 |   }
311 |   
312 |   // Combine all commands
313 |   return [...commonSafeCommands, ...safeCommands, ...approvalCommands, ...forbiddenCommands];
314 | }
315 | 
```

--------------------------------------------------------------------------------
/jest.setup.cjs:
--------------------------------------------------------------------------------

```
  1 | // Mock the ESM modules with CommonJS equivalents for Jest
  2 | const fs = require('fs');
  3 | const path = require('path');
  4 | const { EventEmitter } = require('events');
  5 | const { execFile } = require('child_process');
  6 | const { promisify } = require('util');
  7 | const { randomUUID } = require('crypto');
  8 | 
  9 | // Define the CommandSecurityLevel enum
 10 | const CommandSecurityLevel = {
 11 |   SAFE: 'safe',
 12 |   REQUIRES_APPROVAL: 'requires_approval',
 13 |   FORBIDDEN: 'forbidden'
 14 | };
 15 | 
 16 | // Define the PlatformType enum
 17 | const PlatformType = {
 18 |   WINDOWS: 'windows',
 19 |   MACOS: 'macos',
 20 |   LINUX: 'linux',
 21 |   UNKNOWN: 'unknown'
 22 | };
 23 | 
 24 | // Mock the platform-utils module
 25 | const detectPlatform = () => {
 26 |   const platform = process.platform;
 27 |   if (platform === 'win32') return PlatformType.WINDOWS;
 28 |   if (platform === 'darwin') return PlatformType.MACOS;
 29 |   if (platform === 'linux') return PlatformType.LINUX;
 30 |   return PlatformType.UNKNOWN;
 31 | };
 32 | 
 33 | const getDefaultShell = () => {
 34 |   const platform = detectPlatform();
 35 |   switch (platform) {
 36 |     case PlatformType.WINDOWS:
 37 |       return process.env.COMSPEC || 'cmd.exe';
 38 |     case PlatformType.MACOS:
 39 |       return '/bin/zsh';
 40 |     case PlatformType.LINUX:
 41 |       return process.env.SHELL || '/bin/bash';
 42 |     default:
 43 |       return process.env.SHELL || '/bin/sh';
 44 |   }
 45 | };
 46 | 
 47 | const validateShellPath = (shellPath) => {
 48 |   try {
 49 |     return fs.existsSync(shellPath) && fs.statSync(shellPath).isFile();
 50 |   } catch (error) {
 51 |     return false;
 52 |   }
 53 | };
 54 | 
 55 | const getShellSuggestions = () => ({
 56 |   [PlatformType.WINDOWS]: ['cmd.exe', 'powershell.exe', 'pwsh.exe'],
 57 |   [PlatformType.MACOS]: ['/bin/zsh', '/bin/bash', '/bin/sh'],
 58 |   [PlatformType.LINUX]: ['/bin/bash', '/bin/sh', '/bin/zsh'],
 59 |   [PlatformType.UNKNOWN]: ['/bin/sh']
 60 | });
 61 | 
 62 | const getCommonShellLocations = () => {
 63 |   const platform = detectPlatform();
 64 |   switch (platform) {
 65 |     case PlatformType.WINDOWS:
 66 |       return [
 67 |         process.env.COMSPEC || 'C:\\Windows\\System32\\cmd.exe',
 68 |         'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
 69 |         'C:\\Program Files\\PowerShell\\7\\pwsh.exe'
 70 |       ];
 71 |     case PlatformType.MACOS:
 72 |       return ['/bin/zsh', '/bin/bash', '/bin/sh'];
 73 |     case PlatformType.LINUX:
 74 |       return ['/bin/bash', '/bin/sh', '/usr/bin/bash', '/usr/bin/zsh'];
 75 |     default:
 76 |       return ['/bin/sh'];
 77 |   }
 78 | };
 79 | 
 80 | const getShellConfigurationHelp = () => {
 81 |   const platform = detectPlatform();
 82 |   const suggestions = getShellSuggestions()[platform];
 83 |   const locations = getCommonShellLocations();
 84 |   
 85 |   let message = 'Shell Configuration Help:\n\n';
 86 |   message += `Detected platform: ${platform}\n\n`;
 87 |   message += 'Suggested shells for this platform:\n';
 88 |   suggestions.forEach(shell => {
 89 |     message += `- ${shell}\n`;
 90 |   });
 91 |   
 92 |   message += '\nCommon shell locations on this platform:\n';
 93 |   locations.forEach(location => {
 94 |     message += `- ${location}\n`;
 95 |   });
 96 |   
 97 |   message += '\nTo configure a custom shell, provide the full path to the shell executable.';
 98 |   
 99 |   return message;
100 | };
101 | 
102 | // Mock the CommandService class
103 | class CommandService extends EventEmitter {
104 |   constructor(shell, defaultTimeout = 30000) {
105 |     super();
106 |     this.shell = shell || getDefaultShell();
107 |     this.whitelist = new Map();
108 |     this.pendingCommands = new Map();
109 |     this.defaultTimeout = defaultTimeout;
110 |     this.initializeDefaultWhitelist();
111 |   }
112 | 
113 |   getShell() {
114 |     return this.shell;
115 |   }
116 | 
117 |   initializeDefaultWhitelist() {
118 |     const platform = detectPlatform();
119 |     const commands = [];
120 |     
121 |     // Common commands for all platforms
122 |     commands.push({ command: 'echo', securityLevel: CommandSecurityLevel.SAFE, description: 'Print text to standard output' });
123 |     
124 |     // Platform-specific commands
125 |     if (platform === PlatformType.WINDOWS) {
126 |       commands.push({ command: 'dir', securityLevel: CommandSecurityLevel.SAFE, description: 'List directory contents' });
127 |       commands.push({ command: 'copy', securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL, description: 'Copy files' });
128 |       commands.push({ command: 'mkdir', securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL, description: 'Create directories' });
129 |       commands.push({ command: 'del', securityLevel: CommandSecurityLevel.FORBIDDEN, description: 'Delete files' });
130 |     } else {
131 |       commands.push({ command: 'ls', securityLevel: CommandSecurityLevel.SAFE, description: 'List directory contents' });
132 |       commands.push({ command: 'cat', securityLevel: CommandSecurityLevel.SAFE, description: 'Concatenate and print files' });
133 |       commands.push({ command: 'grep', securityLevel: CommandSecurityLevel.SAFE, description: 'Search for patterns in files' });
134 |       commands.push({ command: 'find', securityLevel: CommandSecurityLevel.SAFE, description: 'Find files in a directory hierarchy' });
135 |       commands.push({ command: 'cd', securityLevel: CommandSecurityLevel.SAFE, description: 'Change directory' });
136 |       commands.push({ command: 'head', securityLevel: CommandSecurityLevel.SAFE, description: 'Output the first part of files' });
137 |       commands.push({ command: 'tail', securityLevel: CommandSecurityLevel.SAFE, description: 'Output the last part of files' });
138 |       commands.push({ command: 'wc', securityLevel: CommandSecurityLevel.SAFE, description: 'Print newline, word, and byte counts' });
139 |       commands.push({ command: 'mv', securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL, description: 'Move (rename) files' });
140 |       commands.push({ command: 'cp', securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL, description: 'Copy files and directories' });
141 |       commands.push({ command: 'mkdir', securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL, description: 'Create directories' });
142 |       commands.push({ command: 'touch', securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL, description: 'Change file timestamps or create empty files' });
143 |       commands.push({ command: 'chmod', securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL, description: 'Change file mode bits' });
144 |       commands.push({ command: 'chown', securityLevel: CommandSecurityLevel.REQUIRES_APPROVAL, description: 'Change file owner and group' });
145 |       commands.push({ command: 'rm', securityLevel: CommandSecurityLevel.FORBIDDEN, description: 'Remove files or directories' });
146 |       commands.push({ command: 'sudo', securityLevel: CommandSecurityLevel.FORBIDDEN, description: 'Execute a command as another user' });
147 |     }
148 |     
149 |     commands.forEach(entry => {
150 |       this.whitelist.set(entry.command, entry);
151 |     });
152 |   }
153 | 
154 |   addToWhitelist(entry) {
155 |     this.whitelist.set(entry.command, entry);
156 |   }
157 | 
158 |   removeFromWhitelist(command) {
159 |     this.whitelist.delete(command);
160 |   }
161 | 
162 |   updateSecurityLevel(command, securityLevel) {
163 |     const entry = this.whitelist.get(command);
164 |     if (entry) {
165 |       entry.securityLevel = securityLevel;
166 |       this.whitelist.set(command, entry);
167 |     }
168 |   }
169 | 
170 |   getWhitelist() {
171 |     return Array.from(this.whitelist.values());
172 |   }
173 | 
174 |   getPendingCommands() {
175 |     return Array.from(this.pendingCommands.values());
176 |   }
177 | 
178 |   validateCommand(command, args) {
179 |     const baseCommand = path.basename(command);
180 |     const entry = this.whitelist.get(baseCommand);
181 |     
182 |     if (!entry) {
183 |       return null;
184 |     }
185 |     
186 |     if (entry.securityLevel === CommandSecurityLevel.FORBIDDEN) {
187 |       return CommandSecurityLevel.FORBIDDEN;
188 |     }
189 |     
190 |     if (entry.allowedArgs && entry.allowedArgs.length > 0) {
191 |       const allArgsValid = args.every((arg, index) => {
192 |         if (index >= (entry.allowedArgs?.length || 0)) {
193 |           return false;
194 |         }
195 |         
196 |         const pattern = entry.allowedArgs?.[index];
197 |         if (!pattern) {
198 |           return false;
199 |         }
200 |         
201 |         if (typeof pattern === 'string') {
202 |           return arg === pattern;
203 |         } else {
204 |           return pattern.test(arg);
205 |         }
206 |       });
207 |       
208 |       if (!allArgsValid) {
209 |         return CommandSecurityLevel.REQUIRES_APPROVAL;
210 |       }
211 |     }
212 |     
213 |     return entry.securityLevel;
214 |   }
215 | 
216 |   async executeCommand(command, args = [], options = {}) {
217 |     const securityLevel = this.validateCommand(command, args);
218 |     
219 |     if (securityLevel === null) {
220 |       throw new Error(`Command not whitelisted: ${command}`);
221 |     }
222 |     
223 |     if (securityLevel === CommandSecurityLevel.FORBIDDEN) {
224 |       throw new Error(`Command is forbidden: ${command}`);
225 |     }
226 |     
227 |     if (securityLevel === CommandSecurityLevel.REQUIRES_APPROVAL) {
228 |       return this.queueCommandForApproval(command, args, options.requestedBy);
229 |     }
230 |     
231 |     try {
232 |       const timeout = options.timeout || this.defaultTimeout;
233 |       const execFileAsync = promisify(execFile);
234 |       const { stdout, stderr } = await execFileAsync(command, args, {
235 |         timeout,
236 |         shell: this.shell
237 |       });
238 |       
239 |       return { stdout, stderr };
240 |     } catch (error) {
241 |       if (error instanceof Error) {
242 |         throw new Error(`Command execution failed: ${error.message}`);
243 |       }
244 |       throw error;
245 |     }
246 |   }
247 | 
248 |   queueCommandForApproval(command, args = [], requestedBy) {
249 |     return new Promise((resolve, reject) => {
250 |       const id = randomUUID();
251 |       const pendingCommand = {
252 |         id,
253 |         command,
254 |         args,
255 |         requestedAt: new Date(),
256 |         requestedBy,
257 |         resolve: (result) => resolve(result),
258 |         reject: (error) => reject(error)
259 |       };
260 |       
261 |       this.pendingCommands.set(id, pendingCommand);
262 |       this.emit('command:pending', pendingCommand);
263 |       
264 |       setTimeout(() => {
265 |         if (this.pendingCommands.has(id)) {
266 |           this.emit('command:approval_timeout', {
267 |             commandId: id,
268 |             message: 'Command approval timed out. If you approved this command in the UI, please use get_pending_commands and approve_command to complete the process.'
269 |           });
270 |         }
271 |       }, 5000);
272 |     });
273 |   }
274 | 
275 |   queueCommandForApprovalNonBlocking(command, args = [], requestedBy) {
276 |     const id = randomUUID();
277 |     const pendingCommand = {
278 |       id,
279 |       command,
280 |       args,
281 |       requestedAt: new Date(),
282 |       requestedBy,
283 |       resolve: () => {},
284 |       reject: () => {}
285 |     };
286 |     
287 |     this.pendingCommands.set(id, pendingCommand);
288 |     this.emit('command:pending', pendingCommand);
289 |     
290 |     setTimeout(() => {
291 |       if (this.pendingCommands.has(id)) {
292 |         this.emit('command:approval_timeout', {
293 |           commandId: id,
294 |           message: 'Command approval timed out. If you approved this command in the UI, please use get_pending_commands and approve_command to complete the process.'
295 |         });
296 |       }
297 |     }, 5000);
298 |     
299 |     return id;
300 |   }
301 | 
302 |   async approveCommand(commandId) {
303 |     const pendingCommand = this.pendingCommands.get(commandId);
304 |     if (!pendingCommand) {
305 |       throw new Error(`No pending command with ID: ${commandId}`);
306 |     }
307 |     
308 |     try {
309 |       const execFileAsync = promisify(execFile);
310 |       const { stdout, stderr } = await execFileAsync(
311 |         pendingCommand.command,
312 |         pendingCommand.args,
313 |         { shell: this.shell }
314 |       );
315 |       
316 |       this.pendingCommands.delete(commandId);
317 |       this.emit('command:approved', { commandId, stdout, stderr });
318 |       pendingCommand.resolve({ stdout, stderr });
319 |       
320 |       return { stdout, stderr };
321 |     } catch (error) {
322 |       this.pendingCommands.delete(commandId);
323 |       this.emit('command:failed', { commandId, error });
324 |       
325 |       if (error instanceof Error) {
326 |         pendingCommand.reject(error);
327 |         throw error;
328 |       }
329 |       
330 |       const genericError = new Error('Command execution failed');
331 |       pendingCommand.reject(genericError);
332 |       throw genericError;
333 |     }
334 |   }
335 | 
336 |   denyCommand(commandId, reason = 'Command denied') {
337 |     const pendingCommand = this.pendingCommands.get(commandId);
338 |     if (!pendingCommand) {
339 |       throw new Error(`No pending command with ID: ${commandId}`);
340 |     }
341 |     
342 |     this.pendingCommands.delete(commandId);
343 |     this.emit('command:denied', { commandId, reason });
344 |     pendingCommand.reject(new Error(reason));
345 |   }
346 | }
347 | 
348 | // Export the mocked modules
349 | module.exports = {
350 |   CommandService,
351 |   CommandSecurityLevel,
352 |   detectPlatform,
353 |   PlatformType,
354 |   getDefaultShell,
355 |   validateShellPath,
356 |   getShellSuggestions,
357 |   getCommonShellLocations,
358 |   getShellConfigurationHelp
359 | };
```

--------------------------------------------------------------------------------
/src/services/command-service.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { execFile } from 'child_process';
  2 | import { promisify } from 'util';
  3 | import { randomUUID } from 'crypto';
  4 | import { EventEmitter } from 'events';
  5 | import * as path from 'path';
  6 | import { getDefaultShell, validateShellPath, getShellConfigurationHelp } from '../utils/platform-utils.js';
  7 | import { getPlatformSpecificCommands } from '../utils/command-whitelist-utils.js';
  8 | 
  9 | const execFileAsync = promisify(execFile);
 10 | 
 11 | /**
 12 |  * Command security level classification
 13 |  */
 14 | export enum CommandSecurityLevel {
 15 |   /** Safe commands that can be executed without approval */
 16 |   SAFE = 'safe',
 17 |   /** Commands that require approval before execution */
 18 |   REQUIRES_APPROVAL = 'requires_approval',
 19 |   /** Commands that are explicitly forbidden */
 20 |   FORBIDDEN = 'forbidden'
 21 | }
 22 | 
 23 | /**
 24 |  * Command whitelist entry
 25 |  */
 26 | export interface CommandWhitelistEntry {
 27 |   /** The command path or name */
 28 |   command: string;
 29 |   /** Security level of the command */
 30 |   securityLevel: CommandSecurityLevel;
 31 |   /** Allowed arguments (string for exact match, RegExp for pattern match) */
 32 |   allowedArgs?: Array<string | RegExp>;
 33 |   /** Description of the command for documentation */
 34 |   description?: string;
 35 | }
 36 | 
 37 | /**
 38 |  * Pending command awaiting approval
 39 |  */
 40 | export interface PendingCommand {
 41 |   /** Unique ID for the command */
 42 |   id: string;
 43 |   /** The command to execute */
 44 |   command: string;
 45 |   /** Arguments for the command */
 46 |   args: string[];
 47 |   /** When the command was requested */
 48 |   requestedAt: Date;
 49 |   /** Who requested the command */
 50 |   requestedBy?: string;
 51 |   /** Resolve function to call when approved */
 52 |   resolve: (value: { stdout: string; stderr: string }) => void;
 53 |   /** Reject function to call when denied */
 54 |   reject: (reason: Error) => void;
 55 | }
 56 | 
 57 | /**
 58 |  * Result of command execution
 59 |  */
 60 | export interface CommandResult {
 61 |   /** Standard output from the command */
 62 |   stdout: string;
 63 |   /** Standard error from the command */
 64 |   stderr: string;
 65 | }
 66 | 
 67 | /**
 68 |  * Service for securely executing shell commands
 69 |  */
 70 | export class CommandService extends EventEmitter {
 71 |   /** Shell to use for commands */
 72 |   private shell: string;
 73 |   /** Command whitelist */
 74 |   private whitelist: Map<string, CommandWhitelistEntry>;
 75 |   /** Pending commands awaiting approval */
 76 |   private pendingCommands: Map<string, PendingCommand>;
 77 |   /** Default timeout for command execution in milliseconds */
 78 |   private defaultTimeout: number;
 79 | 
 80 |   /**
 81 |    * Create a new CommandService
 82 |    * @param shell The shell to use for commands (default: auto-detected based on platform)
 83 |    * @param defaultTimeout Default timeout for command execution in milliseconds (default: 30000)
 84 |    */
 85 |   constructor(shell?: string, defaultTimeout = 30000) {
 86 |     super();
 87 |     this.shell = shell || getDefaultShell();
 88 |     this.whitelist = new Map();
 89 |     this.pendingCommands = new Map();
 90 |     this.defaultTimeout = defaultTimeout;
 91 | 
 92 |     // Initialize with platform-specific commands
 93 |     this.initializeDefaultWhitelist();
 94 |   }
 95 | 
 96 |   /**
 97 |    * Get the current shell being used
 98 |    * @returns The shell path
 99 |    */
100 |   public getShell(): string {
101 |     return this.shell;
102 |   }
103 | 
104 |   /**
105 |    * Initialize the default command whitelist based on the current platform
106 |    */
107 |   private initializeDefaultWhitelist(): void {
108 |     // Get platform-specific commands
109 |     const platformCommands = getPlatformSpecificCommands();
110 |     
111 |     // Add all commands to the whitelist
112 |     platformCommands.forEach(entry => {
113 |       this.whitelist.set(entry.command, entry);
114 |     });
115 |   }
116 | 
117 |   /**
118 |    * Add a command to the whitelist
119 |    * @param entry The command whitelist entry
120 |    */
121 |   public addToWhitelist(entry: CommandWhitelistEntry): void {
122 |     this.whitelist.set(entry.command, entry);
123 |   }
124 | 
125 |   /**
126 |    * Remove a command from the whitelist
127 |    * @param command The command to remove
128 |    */
129 |   public removeFromWhitelist(command: string): void {
130 |     this.whitelist.delete(command);
131 |   }
132 | 
133 |   /**
134 |    * Update a command's security level
135 |    * @param command The command to update
136 |    * @param securityLevel The new security level
137 |    */
138 |   public updateSecurityLevel(command: string, securityLevel: CommandSecurityLevel): void {
139 |     const entry = this.whitelist.get(command);
140 |     if (entry) {
141 |       entry.securityLevel = securityLevel;
142 |       this.whitelist.set(command, entry);
143 |     }
144 |   }
145 | 
146 |   /**
147 |    * Get all whitelisted commands
148 |    * @returns Array of command whitelist entries
149 |    */
150 |   public getWhitelist(): CommandWhitelistEntry[] {
151 |     return Array.from(this.whitelist.values());
152 |   }
153 | 
154 |   /**
155 |    * Get all pending commands awaiting approval
156 |    * @returns Array of pending commands
157 |    */
158 |   public getPendingCommands(): PendingCommand[] {
159 |     return Array.from(this.pendingCommands.values());
160 |   }
161 | 
162 |   /**
163 |    * Validate if a command and its arguments are allowed
164 |    * @param command The command to validate
165 |    * @param args The command arguments
166 |    * @returns The security level of the command or null if not whitelisted
167 |    */
168 |   private validateCommand(command: string, args: string[]): CommandSecurityLevel | null {
169 |     // Extract the base command (without path) using path.basename
170 |     const baseCommand = path.basename(command);
171 |     
172 |     // Check if the command is in the whitelist
173 |     const entry = this.whitelist.get(baseCommand);
174 |     if (!entry) {
175 |       return null;
176 |     }
177 | 
178 |     // If the command is forbidden, return immediately
179 |     if (entry.securityLevel === CommandSecurityLevel.FORBIDDEN) {
180 |       return CommandSecurityLevel.FORBIDDEN;
181 |     }
182 | 
183 |     // If there are allowed arguments defined, validate them
184 |     if (entry.allowedArgs && entry.allowedArgs.length > 0) {
185 |       // Check if all arguments are allowed
186 |       const allArgsValid = args.every((arg, index) => {
187 |         // If we have more args than allowed patterns, reject
188 |         if (index >= (entry.allowedArgs?.length || 0)) {
189 |           return false;
190 |         }
191 | 
192 |         const pattern = entry.allowedArgs?.[index];
193 |         if (!pattern) {
194 |           return false;
195 |         }
196 | 
197 |         // Check if the argument matches the pattern
198 |         if (typeof pattern === 'string') {
199 |           return arg === pattern;
200 |         } else {
201 |           return pattern.test(arg);
202 |         }
203 |       });
204 | 
205 |       if (!allArgsValid) {
206 |         return CommandSecurityLevel.REQUIRES_APPROVAL;
207 |       }
208 |     }
209 | 
210 |     return entry.securityLevel;
211 |   }
212 | 
213 |   /**
214 |    * Execute a shell command
215 |    * @param command The command to execute
216 |    * @param args Command arguments
217 |    * @param options Additional options
218 |    * @returns Promise resolving to command output
219 |    */
220 |   public async executeCommand(
221 |     command: string,
222 |     args: string[] = [],
223 |     options: {
224 |       timeout?: number;
225 |       requestedBy?: string;
226 |     } = {}
227 |   ): Promise<CommandResult> {
228 |     const securityLevel = this.validateCommand(command, args);
229 | 
230 |     // If command is not whitelisted, reject
231 |     if (securityLevel === null) {
232 |       throw new Error(`Command not whitelisted: ${command}`);
233 |     }
234 | 
235 |     // If command is forbidden, reject
236 |     if (securityLevel === CommandSecurityLevel.FORBIDDEN) {
237 |       throw new Error(`Command is forbidden: ${command}`);
238 |     }
239 | 
240 |     // If command requires approval, add to pending queue
241 |     if (securityLevel === CommandSecurityLevel.REQUIRES_APPROVAL) {
242 |       return this.queueCommandForApproval(command, args, options.requestedBy);
243 |     }
244 | 
245 |     // For safe commands, execute immediately
246 |     try {
247 |       const timeout = options.timeout || this.defaultTimeout;
248 |       const { stdout, stderr } = await execFileAsync(command, args, {
249 |         timeout,
250 |         shell: this.shell
251 |       });
252 | 
253 |       return { stdout, stderr };
254 |     } catch (error) {
255 |       if (error instanceof Error) {
256 |         throw new Error(`Command execution failed: ${error.message}`);
257 |       }
258 |       throw error;
259 |     }
260 |   }
261 | 
262 |   /**
263 |    * Queue a command for approval
264 |    * @param command The command to queue
265 |    * @param args Command arguments
266 |    * @param requestedBy Who requested the command
267 |    * @returns Promise resolving when command is approved and executed
268 |    */
269 |   private queueCommandForApproval(
270 |     command: string,
271 |     args: string[] = [],
272 |     requestedBy?: string
273 |   ): Promise<CommandResult> {
274 |     return new Promise((resolve, reject) => {
275 |       const id = randomUUID();
276 |       const pendingCommand: PendingCommand = {
277 |         id,
278 |         command,
279 |         args,
280 |         requestedAt: new Date(),
281 |         requestedBy,
282 |         resolve: (result: CommandResult) => resolve(result),
283 |         reject: (error: Error) => reject(error)
284 |       };
285 | 
286 |       this.pendingCommands.set(id, pendingCommand);
287 |       
288 |       // Emit event for pending command
289 |       this.emit('command:pending', pendingCommand);
290 |       
291 |       // Set a timeout to check if the command is still pending after a while
292 |       // This helps detect if the UI approval didn't properly trigger the approveCommand method
293 |       setTimeout(() => {
294 |         // If the command is still pending after the timeout
295 |         if (this.pendingCommands.has(id)) {
296 |           // Emit a warning event that can be handled by the client
297 |           this.emit('command:approval_timeout', {
298 |             commandId: id,
299 |             message: 'Command approval timed out. If you approved this command in the UI, please use get_pending_commands and approve_command to complete the process.'
300 |           });
301 |         }
302 |       }, 5000); // 5 second timeout to detect UI approval issues
303 |     });
304 |   }
305 | 
306 |   /**
307 |    * Queue a command for approval without waiting for the Promise to resolve
308 |    * @param command The command to queue
309 |    * @param args Command arguments
310 |    * @param requestedBy Who requested the command
311 |    * @returns The ID of the queued command
312 |    */
313 |   public queueCommandForApprovalNonBlocking(
314 |     command: string,
315 |     args: string[] = [],
316 |     requestedBy?: string
317 |   ): string {
318 |     const id = randomUUID();
319 |     const pendingCommand: PendingCommand = {
320 |       id,
321 |       command,
322 |       args,
323 |       requestedAt: new Date(),
324 |       requestedBy,
325 |       resolve: () => {}, // No-op resolve function
326 |       reject: () => {}   // No-op reject function
327 |     };
328 | 
329 |     this.pendingCommands.set(id, pendingCommand);
330 |     
331 |     // Emit event for pending command
332 |     this.emit('command:pending', pendingCommand);
333 |     
334 |     // Set a timeout to check if the command is still pending after a while
335 |     setTimeout(() => {
336 |       // If the command is still pending after the timeout
337 |       if (this.pendingCommands.has(id)) {
338 |         // Emit a warning event that can be handled by the client
339 |         this.emit('command:approval_timeout', {
340 |           commandId: id,
341 |           message: 'Command approval timed out. If you approved this command in the UI, please use get_pending_commands and approve_command to complete the process.'
342 |         });
343 |       }
344 |     }, 5000); // 5 second timeout to detect UI approval issues
345 |     
346 |     return id;
347 |   }
348 | 
349 |   /**
350 |    * Approve a pending command
351 |    * @param commandId ID of the command to approve
352 |    * @returns Promise resolving to command output
353 |    */
354 |   public async approveCommand(commandId: string): Promise<CommandResult> {
355 |     const pendingCommand = this.pendingCommands.get(commandId);
356 |     if (!pendingCommand) {
357 |       throw new Error(`No pending command with ID: ${commandId}`);
358 |     }
359 | 
360 |     try {
361 |       const { stdout, stderr } = await execFileAsync(
362 |         pendingCommand.command,
363 |         pendingCommand.args,
364 |         { shell: this.shell }
365 |       );
366 | 
367 |       // Remove from pending queue
368 |       this.pendingCommands.delete(commandId);
369 |       
370 |       // Emit event for approved command
371 |       this.emit('command:approved', { commandId, stdout, stderr });
372 |       
373 |       // Resolve the original promise
374 |       pendingCommand.resolve({ stdout, stderr });
375 |       
376 |       return { stdout, stderr };
377 |     } catch (error) {
378 |       // Remove from pending queue
379 |       this.pendingCommands.delete(commandId);
380 |       
381 |       // Emit event for failed command
382 |       this.emit('command:failed', { commandId, error });
383 |       
384 |       if (error instanceof Error) {
385 |         // Reject the original promise
386 |         pendingCommand.reject(error);
387 |         throw error;
388 |       }
389 |       
390 |       const genericError = new Error('Command execution failed');
391 |       pendingCommand.reject(genericError);
392 |       throw genericError;
393 |     }
394 |   }
395 | 
396 |   /**
397 |    * Deny a pending command
398 |    * @param commandId ID of the command to deny
399 |    * @param reason Reason for denial
400 |    */
401 |   public denyCommand(commandId: string, reason: string = 'Command denied'): void {
402 |     const pendingCommand = this.pendingCommands.get(commandId);
403 |     if (!pendingCommand) {
404 |       throw new Error(`No pending command with ID: ${commandId}`);
405 |     }
406 | 
407 |     // Remove from pending queue
408 |     this.pendingCommands.delete(commandId);
409 |     
410 |     // Emit event for denied command
411 |     this.emit('command:denied', { commandId, reason });
412 |     
413 |     // Reject the original promise
414 |     pendingCommand.reject(new Error(reason));
415 |   }
416 | }
417 | 
```

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

```typescript
  1 | #!/usr/bin/env node
  2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4 | import {
  5 |   CallToolRequestSchema,
  6 |   ErrorCode,
  7 |   ListToolsRequestSchema,
  8 |   McpError,
  9 | } from '@modelcontextprotocol/sdk/types.js';
 10 | import { z } from 'zod';
 11 | import * as path from 'path';
 12 | import * as fs from 'fs';
 13 | import { execFile } from 'child_process';
 14 | import { promisify } from 'util';
 15 | import { randomUUID } from 'crypto';
 16 | import { fileURLToPath } from 'url';
 17 | import { dirname } from 'path';
 18 | import { CommandService, CommandSecurityLevel } from './services/command-service.js';
 19 | import { getLogger, Logger } from './utils/logger.js';
 20 | 
 21 | // In ESM, __dirname is not available directly, so we create it
 22 | const __filename = fileURLToPath(import.meta.url);
 23 | const __dirname = dirname(__filename);
 24 | 
 25 | const execFileAsync = promisify(execFile);
 26 | // Initialize the logger
 27 | // Use __dirname to get the directory of the current file
 28 | const LOG_FILE = path.join(__dirname, '../logs/super-shell-mcp.log');
 29 | console.error(`Log file path: ${LOG_FILE}`);
 30 | const logger = getLogger(LOG_FILE, true);
 31 | 
 32 | /**
 33 |  * SuperShellMcpServer - MCP server for executing shell commands across multiple platforms
 34 |  */
 35 | class SuperShellMcpServer {
 36 |   private server: Server;
 37 |   private commandService: CommandService;
 38 |   private pendingApprovals: Map<string, { command: string; args: string[] }>;
 39 | 
 40 |   constructor(options?: { shell?: string }) {
 41 |     // Initialize the command service with auto-detected or specified shell
 42 |     this.commandService = new CommandService(options?.shell);
 43 |     this.pendingApprovals = new Map();
 44 | 
 45 |     // Initialize the MCP server
 46 |     this.server = new Server(
 47 |       {
 48 |         name: 'super-shell-mcp',
 49 |         version: '2.0.13',
 50 |       },
 51 |       {
 52 |         capabilities: {
 53 |           tools: {},
 54 |         },
 55 |       }
 56 |     );
 57 | 
 58 |     // Set up event handlers for command service
 59 |     this.setupCommandServiceEvents();
 60 | 
 61 |     // Set up MCP request handlers
 62 |     this.setupRequestHandlers();
 63 |     
 64 |     // Error handling
 65 |     this.server.onerror = (error) => {
 66 |       logger.error(`[MCP Error] ${error}`);
 67 |       console.error('[MCP Error]', error);
 68 |     };
 69 |     
 70 |     process.on('SIGINT', async () => {
 71 |       logger.info('Received SIGINT signal, shutting down');
 72 |       await this.server.close();
 73 |       logger.info('Server closed, exiting process');
 74 |       logger.close();
 75 |       process.exit(0);
 76 |     });
 77 |   }
 78 | 
 79 |   /**
 80 |    * Set up event handlers for the command service
 81 |    */
 82 |   private setupCommandServiceEvents(): void {
 83 |     this.commandService.on('command:pending', (pendingCommand) => {
 84 |       logger.info(`[Pending Command] ID: ${pendingCommand.id}, Command: ${pendingCommand.command} ${pendingCommand.args.join(' ')}`);
 85 |       console.error(`[Pending Command] ID: ${pendingCommand.id}, Command: ${pendingCommand.command} ${pendingCommand.args.join(' ')}`);
 86 |       this.pendingApprovals.set(pendingCommand.id, {
 87 |         command: pendingCommand.command,
 88 |         args: pendingCommand.args,
 89 |       });
 90 |     });
 91 | 
 92 |     this.commandService.on('command:approved', (data) => {
 93 |       logger.info(`[Approved Command] ID: ${data.commandId}`);
 94 |       console.error(`[Approved Command] ID: ${data.commandId}`);
 95 |       this.pendingApprovals.delete(data.commandId);
 96 |     });
 97 | 
 98 |     this.commandService.on('command:denied', (data) => {
 99 |       logger.info(`[Denied Command] ID: ${data.commandId}, Reason: ${data.reason}`);
100 |       console.error(`[Denied Command] ID: ${data.commandId}, Reason: ${data.reason}`);
101 |       this.pendingApprovals.delete(data.commandId);
102 |     });
103 | 
104 |     this.commandService.on('command:failed', (data) => {
105 |       logger.error(`[Failed Command] ID: ${data.commandId}, Error: ${data.error.message}`);
106 |       console.error(`[Failed Command] ID: ${data.commandId}, Error: ${data.error.message}`);
107 |       this.pendingApprovals.delete(data.commandId);
108 |     });
109 |     
110 |     // Handle approval timeout events
111 |     this.commandService.on('command:approval_timeout', (data) => {
112 |       logger.error(`[Approval Timeout] ID: ${data.commandId}, Message: ${data.message}`);
113 |       console.error(`[Approval Timeout] ID: ${data.commandId}, Message: ${data.message}`);
114 |       // Log the timeout but keep the command in the pending queue
115 |       // The AI assistant will need to use get_pending_commands and approve_command to proceed
116 |     });
117 |   }
118 | 
119 |   /**
120 |    * Set up MCP request handlers
121 |    */
122 |   private setupRequestHandlers(): void {
123 |     // List available tools
124 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
125 |       tools: [
126 |         {
127 |           name: 'get_platform_info',
128 |           description: 'Get information about the current platform and shell',
129 |           inputSchema: {
130 |             type: 'object',
131 |             properties: {},
132 |           },
133 |         },
134 |         {
135 |           name: 'execute_command',
136 |           description: 'Execute a shell command on the current platform',
137 |           inputSchema: {
138 |             type: 'object',
139 |             properties: {
140 |               command: {
141 |                 type: 'string',
142 |                 description: 'The command to execute',
143 |               },
144 |               args: {
145 |                 type: 'array',
146 |                 items: {
147 |                   type: 'string',
148 |                 },
149 |                 description: 'Command arguments',
150 |               },
151 |             },
152 |             required: ['command'],
153 |           },
154 |         },
155 |         {
156 |           name: 'get_whitelist',
157 |           description: 'Get the list of whitelisted commands',
158 |           inputSchema: {
159 |             type: 'object',
160 |             properties: {},
161 |           },
162 |         },
163 |         {
164 |           name: 'add_to_whitelist',
165 |           description: 'Add a command to the whitelist',
166 |           inputSchema: {
167 |             type: 'object',
168 |             properties: {
169 |               command: {
170 |                 type: 'string',
171 |                 description: 'The command to whitelist',
172 |               },
173 |               securityLevel: {
174 |                 type: 'string',
175 |                 enum: ['safe', 'requires_approval', 'forbidden'],
176 |                 description: 'Security level for the command',
177 |               },
178 |               description: {
179 |                 type: 'string',
180 |                 description: 'Description of the command',
181 |               },
182 |             },
183 |             required: ['command', 'securityLevel'],
184 |           },
185 |         },
186 |         {
187 |           name: 'update_security_level',
188 |           description: 'Update the security level of a whitelisted command',
189 |           inputSchema: {
190 |             type: 'object',
191 |             properties: {
192 |               command: {
193 |                 type: 'string',
194 |                 description: 'The command to update',
195 |               },
196 |               securityLevel: {
197 |                 type: 'string',
198 |                 enum: ['safe', 'requires_approval', 'forbidden'],
199 |                 description: 'New security level for the command',
200 |               },
201 |             },
202 |             required: ['command', 'securityLevel'],
203 |           },
204 |         },
205 |         {
206 |           name: 'remove_from_whitelist',
207 |           description: 'Remove a command from the whitelist',
208 |           inputSchema: {
209 |             type: 'object',
210 |             properties: {
211 |               command: {
212 |                 type: 'string',
213 |                 description: 'The command to remove from whitelist',
214 |               },
215 |             },
216 |             required: ['command'],
217 |           },
218 |         },
219 |         {
220 |           name: 'get_pending_commands',
221 |           description: 'Get the list of commands pending approval',
222 |           inputSchema: {
223 |             type: 'object',
224 |             properties: {},
225 |           },
226 |         },
227 |         {
228 |           name: 'approve_command',
229 |           description: 'Approve a pending command',
230 |           inputSchema: {
231 |             type: 'object',
232 |             properties: {
233 |               commandId: {
234 |                 type: 'string',
235 |                 description: 'ID of the command to approve',
236 |               },
237 |             },
238 |             required: ['commandId'],
239 |           },
240 |         },
241 |         {
242 |           name: 'deny_command',
243 |           description: 'Deny a pending command',
244 |           inputSchema: {
245 |             type: 'object',
246 |             properties: {
247 |               commandId: {
248 |                 type: 'string',
249 |                 description: 'ID of the command to deny',
250 |               },
251 |               reason: {
252 |                 type: 'string',
253 |                 description: 'Reason for denial',
254 |               },
255 |             },
256 |             required: ['commandId'],
257 |           },
258 |         },
259 |       ],
260 |     }));
261 | 
262 |     // Handle tool calls
263 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
264 |       const { name, arguments: args } = request.params;
265 | 
266 |       try {
267 |         switch (name) {
268 |           case 'get_platform_info':
269 |             return await this.handleGetPlatformInfo();
270 |           case 'execute_command':
271 |             return await this.handleExecuteCommand(args);
272 |           case 'get_whitelist':
273 |             return await this.handleGetWhitelist();
274 |           case 'add_to_whitelist':
275 |             return await this.handleAddToWhitelist(args);
276 |           case 'update_security_level':
277 |             return await this.handleUpdateSecurityLevel(args);
278 |           case 'remove_from_whitelist':
279 |             return await this.handleRemoveFromWhitelist(args);
280 |           case 'get_pending_commands':
281 |             return await this.handleGetPendingCommands();
282 |           case 'approve_command':
283 |             return await this.handleApproveCommand(args);
284 |           case 'deny_command':
285 |             return await this.handleDenyCommand(args);
286 |           default:
287 |             throw new McpError(
288 |               ErrorCode.MethodNotFound,
289 |               `Unknown tool: ${name}`
290 |             );
291 |         }
292 |       } catch (error) {
293 |         if (error instanceof McpError) {
294 |           throw error;
295 |         }
296 |         
297 |         if (error instanceof Error) {
298 |           return {
299 |             content: [
300 |               {
301 |                 type: 'text',
302 |                 text: `Error: ${error.message}`,
303 |               },
304 |             ],
305 |             isError: true,
306 |           };
307 |         }
308 |         
309 |         throw new McpError(
310 |           ErrorCode.InternalError,
311 |           'An unexpected error occurred'
312 |         );
313 |       }
314 |     });
315 |   }
316 | 
317 |   /**
318 |    * Handle execute_command tool
319 |    */
320 |   private async handleExecuteCommand(args: any) {
321 |     const schema = z.object({
322 |       command: z.string(),
323 |       args: z.array(z.string()).optional(),
324 |     });
325 | 
326 |     // Log the start of command execution
327 |     logger.debug(`handleExecuteCommand called with args: ${JSON.stringify(args)}`);
328 | 
329 |     const { command, args: commandArgs = [] } = schema.parse(args);
330 | 
331 |     // Extract the base command (without path)
332 |     const baseCommand = path.basename(command);
333 |     
334 |     logger.debug(`[Executing Command] Command: ${command} ${commandArgs.join(' ')}`);
335 |     logger.debug(`Base command: ${baseCommand}`);
336 |     
337 |     // Check if the command requires approval before attempting execution
338 |     const whitelist = this.commandService.getWhitelist();
339 |     logger.debug(`Whitelist entries: ${whitelist.length}`);
340 |     
341 |     const whitelistEntry = whitelist.find(entry => entry.command === baseCommand);
342 |     logger.debug(`Whitelist entry found: ${whitelistEntry ? 'yes' : 'no'}`);
343 |     
344 |     if (whitelistEntry) {
345 |       logger.debug(`Security level: ${whitelistEntry.securityLevel}`);
346 |     }
347 |     
348 |     if (whitelistEntry && whitelistEntry.securityLevel === CommandSecurityLevel.REQUIRES_APPROVAL) {
349 |       logger.debug(`[Command Requires Approval] Command: ${command} ${commandArgs.join(' ')}`);
350 |       
351 |       // Use the non-blocking method to queue the command for approval
352 |       const commandId = this.commandService.queueCommandForApprovalNonBlocking(command, commandArgs);
353 |       logger.debug(`Command queued for approval with ID: ${commandId}`);
354 |       
355 |       // Return immediately with instructions for approval
356 |       logger.debug(`Returning response to client`);
357 |       return {
358 |         content: [
359 |           {
360 |             type: 'text',
361 |             text: `This command requires approval. It has been queued with ID: ${commandId}\n\nPlease approve this command in the UI or use the 'approve_command' function with this command ID.`,
362 |           },
363 |         ],
364 |         isError: false, // Not an error, just needs approval
365 |       };
366 |     }
367 |     
368 |     // For safe commands or forbidden commands, use the normal execution path
369 |     try {
370 |       // Use the CommandService's executeCommand method
371 |       const result = await this.commandService.executeCommand(command, commandArgs);
372 |       
373 |       return {
374 |         content: [
375 |           {
376 |             type: 'text',
377 |             text: result.stdout,
378 |           },
379 |           {
380 |             type: 'text',
381 |             text: result.stderr ? `Error output: ${result.stderr}` : '',
382 |           },
383 |         ],
384 |       };
385 |     } catch (error: unknown) {
386 |       const errorMessage = error instanceof Error
387 |         ? error.message
388 |         : 'Unknown error occurred';
389 |       
390 |       console.error(`[Command Execution Failed] Error: ${errorMessage}`);
391 |       
392 |       return {
393 |         content: [
394 |           {
395 |             type: 'text',
396 |             text: `Command execution failed: ${errorMessage}`,
397 |           },
398 |         ],
399 |         isError: true,
400 |       };
401 |     }
402 |   }
403 | 
404 |   /**
405 |    * Handle get_whitelist tool
406 |    */
407 |   private async handleGetWhitelist() {
408 |     const whitelist = this.commandService.getWhitelist();
409 |     
410 |     return {
411 |       content: [
412 |         {
413 |           type: 'text',
414 |           text: JSON.stringify(whitelist, null, 2),
415 |         },
416 |       ],
417 |     };
418 |   }
419 | 
420 |   /**
421 |    * Handle add_to_whitelist tool
422 |    */
423 |   private async handleAddToWhitelist(args: any) {
424 |     const schema = z.object({
425 |       command: z.string(),
426 |       securityLevel: z.enum(['safe', 'requires_approval', 'forbidden']),
427 |       description: z.string().optional(),
428 |     });
429 | 
430 |     const { command, securityLevel, description } = schema.parse(args);
431 | 
432 |     // Map string security level to enum
433 |     const securityLevelEnum = securityLevel === 'safe'
434 |       ? CommandSecurityLevel.SAFE
435 |       : securityLevel === 'requires_approval'
436 |         ? CommandSecurityLevel.REQUIRES_APPROVAL
437 |         : CommandSecurityLevel.FORBIDDEN;
438 | 
439 |     this.commandService.addToWhitelist({
440 |       command,
441 |       securityLevel: securityLevelEnum,
442 |       description,
443 |     });
444 | 
445 |     return {
446 |       content: [
447 |         {
448 |           type: 'text',
449 |           text: `Command '${command}' added to whitelist with security level '${securityLevel}'`,
450 |         },
451 |       ],
452 |     };
453 |   }
454 | 
455 |   /**
456 |    * Handle update_security_level tool
457 |    */
458 |   private async handleUpdateSecurityLevel(args: any) {
459 |     const schema = z.object({
460 |       command: z.string(),
461 |       securityLevel: z.enum(['safe', 'requires_approval', 'forbidden']),
462 |     });
463 | 
464 |     const { command, securityLevel } = schema.parse(args);
465 | 
466 |     // Map string security level to enum
467 |     const securityLevelEnum = securityLevel === 'safe'
468 |       ? CommandSecurityLevel.SAFE
469 |       : securityLevel === 'requires_approval'
470 |         ? CommandSecurityLevel.REQUIRES_APPROVAL
471 |         : CommandSecurityLevel.FORBIDDEN;
472 | 
473 |     this.commandService.updateSecurityLevel(command, securityLevelEnum);
474 | 
475 |     return {
476 |       content: [
477 |         {
478 |           type: 'text',
479 |           text: `Security level for command '${command}' updated to '${securityLevel}'`,
480 |         },
481 |       ],
482 |     };
483 |   }
484 | 
485 |   /**
486 |    * Handle remove_from_whitelist tool
487 |    */
488 |   private async handleRemoveFromWhitelist(args: any) {
489 |     const schema = z.object({
490 |       command: z.string(),
491 |     });
492 | 
493 |     const { command } = schema.parse(args);
494 | 
495 |     this.commandService.removeFromWhitelist(command);
496 | 
497 |     return {
498 |       content: [
499 |         {
500 |           type: 'text',
501 |           text: `Command '${command}' removed from whitelist`,
502 |         },
503 |       ],
504 |     };
505 |   }
506 | 
507 |   /**
508 |    * Handle get_platform_info tool
509 |    */
510 |   private async handleGetPlatformInfo() {
511 |     const { detectPlatform, getDefaultShell, getShellSuggestions, getCommonShellLocations } = await import('./utils/platform-utils.js');
512 |     
513 |     const platform = detectPlatform();
514 |     const currentShell = this.commandService.getShell();
515 |     const suggestedShells = getShellSuggestions()[platform];
516 |     const commonLocations = getCommonShellLocations();
517 |     
518 |     return {
519 |       content: [
520 |         {
521 |           type: 'text',
522 |           text: JSON.stringify({
523 |             platform,
524 |             currentShell,
525 |             suggestedShells,
526 |             commonLocations,
527 |             helpMessage: `Super Shell MCP is running on ${platform} using ${currentShell}`
528 |           }, null, 2),
529 |         },
530 |       ],
531 |     };
532 |   }
533 | 
534 |   /**
535 |    * Handle get_pending_commands tool
536 |    */
537 |   private async handleGetPendingCommands() {
538 |     const pendingCommands = this.commandService.getPendingCommands();
539 |     
540 |     return {
541 |       content: [
542 |         {
543 |           type: 'text',
544 |           text: JSON.stringify(pendingCommands.map(cmd => ({
545 |             id: cmd.id,
546 |             command: cmd.command,
547 |             args: cmd.args,
548 |             requestedAt: cmd.requestedAt,
549 |             requestedBy: cmd.requestedBy,
550 |           })), null, 2),
551 |         },
552 |       ],
553 |     };
554 |   }
555 | 
556 |   /**
557 |    * Handle approve_command tool
558 |    */
559 |   private async handleApproveCommand(args: any) {
560 |     const schema = z.object({
561 |       commandId: z.string(),
562 |     });
563 | 
564 |     logger.debug(`handleApproveCommand called with args: ${JSON.stringify(args)}`);
565 | 
566 |     const { commandId } = schema.parse(args);
567 | 
568 |     // Log the approval attempt
569 |     logger.debug(`[Approval Attempt] ID: ${commandId}`);
570 | 
571 |     // Check if the command exists in our local pending approvals map
572 |     const localPending = this.pendingApprovals.has(commandId);
573 |     logger.debug(`Command found in local pendingApprovals: ${localPending ? 'yes' : 'no'}`);
574 | 
575 |     // Check if the command exists in the CommandService's pending queue
576 |     const pendingCommands = this.commandService.getPendingCommands();
577 |     logger.debug(`CommandService pending commands: ${pendingCommands.length}`);
578 |     const pendingCommand = pendingCommands.find(cmd => cmd.id === commandId);
579 |     logger.debug(`Command found in CommandService pending queue: ${pendingCommand ? 'yes' : 'no'}`);
580 |     
581 |     if (pendingCommand) {
582 |       logger.debug(`Pending command details: ${JSON.stringify({
583 |         id: pendingCommand.id,
584 |         command: pendingCommand.command,
585 |         args: pendingCommand.args,
586 |         requestedAt: pendingCommand.requestedAt
587 |       })}`);
588 |     }
589 | 
590 |     try {
591 |       logger.debug(`Calling CommandService.approveCommand with ID: ${commandId}`);
592 |       // Use the CommandService's approveCommand method directly
593 |       const result = await this.commandService.approveCommand(commandId);
594 |       
595 |       logger.debug(`[Command Approved] ID: ${commandId}, Output length: ${result.stdout.length}`);
596 |       logger.debug(`Command output: ${result.stdout.substring(0, 100)}${result.stdout.length > 100 ? '...' : ''}`);
597 |       
598 |       return {
599 |         content: [
600 |           {
601 |             type: 'text',
602 |             text: `Command approved and executed successfully.\nOutput: ${result.stdout}`,
603 |           },
604 |           {
605 |             type: 'text',
606 |             text: result.stderr ? `Error output: ${result.stderr}` : '',
607 |           },
608 |         ],
609 |       };
610 |     } catch (error) {
611 |       logger.error(`[Approval Error] ID: ${commandId}, Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
612 |       
613 |       if (error instanceof Error) {
614 |         return {
615 |           content: [
616 |             {
617 |               type: 'text',
618 |               text: `Command approval failed: ${error.message}`,
619 |             },
620 |           ],
621 |           isError: true,
622 |         };
623 |       }
624 |       throw error;
625 |     }
626 |   }
627 | 
628 |   /**
629 |    * Handle deny_command tool
630 |    */
631 |   private async handleDenyCommand(args: any) {
632 |     const schema = z.object({
633 |       commandId: z.string(),
634 |       reason: z.string().optional(),
635 |     });
636 | 
637 |     logger.debug(`handleDenyCommand called with args: ${JSON.stringify(args)}`);
638 | 
639 |     const { commandId, reason } = schema.parse(args);
640 | 
641 |     logger.debug(`[Denial Attempt] ID: ${commandId}, Reason: ${reason || 'none provided'}`);
642 | 
643 |     try {
644 |       this.commandService.denyCommand(commandId, reason);
645 |       logger.info(`Command denied: ID: ${commandId}, Reason: ${reason || 'none provided'}`);
646 |       
647 |       return {
648 |         content: [
649 |           {
650 |             type: 'text',
651 |             text: `Command denied${reason ? `: ${reason}` : ''}`,
652 |           },
653 |         ],
654 |       };
655 |     } catch (error) {
656 |       logger.error(`[Denial Error] ID: ${commandId}, Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
657 |       
658 |       if (error instanceof Error) {
659 |         return {
660 |           content: [
661 |             {
662 |               type: 'text',
663 |               text: `Command denial failed: ${error.message}`,
664 |             },
665 |           ],
666 |           isError: true,
667 |         };
668 |       }
669 |       throw error;
670 |     }
671 |   }
672 | 
673 |   /**
674 |    * Run the MCP server
675 |    */
676 |   async run() {
677 |     logger.info('Starting Super Shell MCP server');
678 |     const transport = new StdioServerTransport();
679 |     await this.server.connect(transport);
680 |     logger.info('Super Shell MCP server running on stdio');
681 |     console.error('Super Shell MCP server running on stdio');
682 |     console.error(`Log file: ${LOG_FILE}`);
683 |     logger.info(`Log file: ${LOG_FILE}`);
684 |   }
685 | }
686 | 
687 | // Create and run the server
688 | const server = new SuperShellMcpServer();
689 | server.run().catch(console.error);
```