#
tokens: 6567/50000 15/15 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .changeset
│   ├── config.json
│   └── README.md
├── .gitignore
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE
├── package.json
├── pnpm-lock.yaml
├── README.md
├── renovate.json
├── src
│   ├── command-executor.ts
│   ├── constants.ts
│   ├── errors.ts
│   ├── index.ts
│   └── types.ts
└── tsconfig.json
```

# Files

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

```
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
```

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

```
{
	"useTabs": true,
	"singleQuote": true,
	"trailingComma": "all",
	"printWidth": 70,
	"proseWrap": "always"
}
```

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

```
# Dependencies
node_modules/
.pnpm-store/

# Build output
dist/
build/

# Environment variables
.env
.env.local
.env.*.local

# IDE
.vscode/
.idea/
*.swp
*.swo

# Logs
*.log
npm-debug.log*
pnpm-debug.log*

# Testing
coverage/

# Database files
*.db
*.db-journal

# OS
.DS_Store
Thumbs.db
```

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

```markdown
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

```

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

```markdown
# mcp-wsl-exec

A Model Context Protocol (MCP) server for **Windows + Claude Desktop users** to interact with Windows Subsystem for Linux (WSL). Provides both read-only information gathering and secure command execution capabilities.

<a href="https://glama.ai/mcp/servers/wv6df94kb8">
  <img width="380" height="200" src="https://glama.ai/mcp/servers/wv6df94kb8/badge" />
</a>

## ⚠️ Important: Who Should Use This?

**✅ You SHOULD use this if:**
- You're using **Claude Desktop on Windows**
- You need to interact with your WSL environment
- You want to provide WSL context to Claude (system info, processes, files, etc.)

**❌ You DON'T need this if:**
- You're using **Claude Code** (it has native bash access)
- You're on Linux/macOS (use native tools instead)
- You only need Windows PowerShell/CMD (use a different MCP server)

## Features

### 📊 Information Gathering (Read-Only)
- 🖥️ Get system information (OS, kernel, hostname)
- 📁 Browse directory contents
- 💾 Check disk usage
- ⚙️ List environment variables
- 🔄 Monitor running processes

### 🔧 Command Execution (With Safety)
- 🔒 Secure command execution in WSL environments
- ⚡ Built-in safety features:
  - Dangerous command detection
  - Command confirmation system
  - Path traversal prevention
  - Command sanitization
- 📁 Working directory support
- ⏱️ Command timeout functionality
- 🛡️ Protection against shell injection

## Configuration

This server requires configuration through your MCP client. Here are
examples for different environments:

### Cline Configuration

Add this to your Cline MCP settings:

```json
{
	"mcpServers": {
		"mcp-wsl-exec": {
			"command": "npx",
			"args": ["-y", "mcp-wsl-exec"]
		}
	}
}
```

### Claude Desktop Configuration

Add this to your Claude Desktop configuration:

```json
{
	"mcpServers": {
		"mcp-wsl-exec": {
			"command": "npx",
			"args": ["-y", "mcp-wsl-exec"]
		}
	}
}
```

## API

The server provides 7 MCP tools:

### Information Gathering (Read-Only) 📊

These tools provide context about your WSL environment without making changes:

#### get_system_info

Get system information (OS version, kernel, hostname).

**Parameters:** None

#### get_directory_info

Get directory contents and file information.

**Parameters:**
- `path` (string, optional): Directory path (defaults to current directory)
- `details` (boolean, optional): Show detailed information (permissions, sizes, etc.)

#### get_disk_usage

Get disk space information.

**Parameters:**
- `path` (string, optional): Specific path to check (defaults to all filesystems)

#### get_environment

Get environment variables.

**Parameters:**
- `filter` (string, optional): Filter pattern to search for specific variables

#### list_processes

List running processes.

**Parameters:**
- `filter` (string, optional): Filter by process name

### Command Execution (Potentially Destructive) 🔧

Use these tools when you need to make changes or run custom commands:

#### execute_command

Execute a command in WSL with safety checks and validation.

**Parameters:**
- `command` (string, required): Command to execute
- `working_dir` (string, optional): Working directory for command execution
- `timeout` (number, optional): Timeout in milliseconds

**Note:** Dangerous commands will require confirmation via `confirm_command`.

#### confirm_command

Confirm execution of a dangerous command that was flagged by safety checks.

**Parameters:**
- `confirmation_id` (string, required): Confirmation ID received from execute_command
- `confirm` (boolean, required): Whether to proceed with the command execution

## Safety Features

### Dangerous Command Detection

The server maintains a list of potentially dangerous commands that
require explicit confirmation before execution, including:

- File system operations (rm, rmdir, mv)
- System commands (shutdown, reboot)
- Package management (apt, yum, dnf)
- File redirections (>, >>)
- Permission changes (chmod, chown)
- And more...

### Command Sanitization

All commands are sanitized to prevent:

- Shell metacharacter injection
- Path traversal attempts
- Home directory references
- Dangerous command chaining

## Development

### Setup

1. Clone the repository
2. Install dependencies:

```bash
pnpm install
```

3. Build the project:

```bash
pnpm build
```

4. Run in development mode:

```bash
pnpm dev
```

### Publishing

The project uses changesets for version management. To publish:

1. Create a changeset:

```bash
pnpm changeset
```

2. Version the package:

```bash
pnpm changeset version
```

3. Publish to npm:

```bash
pnpm release
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

MIT License - see the [LICENSE](LICENSE) file for details.

## Acknowledgments

- Built on the
  [Model Context Protocol](https://github.com/modelcontextprotocol)
- Designed for secure WSL command execution

```

--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended"
  ]
}
```

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

```markdown
# mcp-wsl-exec

## 0.0.3

### Patch Changes

- dd1155f: Migrate to tmcp, add read-only info tools, clarify
  Windows+Claude Desktop focus

## 0.0.2

### Patch Changes

- glama badge

## 0.0.1

### Patch Changes

- init

```

--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}
```

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

```json
{
	"compilerOptions": {
		"target": "ES2020",
		"module": "ES2020",
		"moduleResolution": "node",
		"esModuleInterop": true,
		"strict": true,
		"outDir": "dist",
		"rootDir": "src",
		"skipLibCheck": true,
		"forceConsistentCasingInFileNames": true,
		"resolveJsonModule": true
	},
	"include": ["src/**/*"],
	"exclude": ["node_modules", "dist"]
}

```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
export interface CommandResponse {
	stdout: string;
	stderr: string;
	exit_code: number | null;
	command: string;
	requires_confirmation?: boolean;
	error?: string;
	working_dir?: string;
}

export interface PendingConfirmation {
	command: string;
	working_dir?: string;
	timeout?: number;
	resolve: (value: CommandResponse) => void;
	reject: (reason?: any) => void;
}

```

--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------

```typescript
// Define dangerous commands that require confirmation
export const dangerous_commands = [
	'rm',
	'rmdir',
	'dd',
	'mkfs',
	'mkswap',
	'fdisk',
	'shutdown',
	'reboot',
	'>', // redirect that could overwrite
	'>>', // append redirect that could modify files
	'format',
	'chmod',
	'chown',
	'sudo',
	'su',
	'passwd',
	'mv', // moving files can be dangerous
	'find -delete',
	'truncate',
	'shred',
	'kill',
	'pkill',
	'service',
	'systemctl',
	'mount',
	'umount',
	'apt',
	'apt-get',
	'dpkg',
	'yum',
	'dnf',
	'pacman',
] as const;

// WSL process configuration
export const wsl_config = {
	executable: 'wsl.exe',
	shell: 'bash',
	default_timeout: 30000, // 30 seconds
} as const;

```

--------------------------------------------------------------------------------
/src/errors.ts:
--------------------------------------------------------------------------------

```typescript
export class WslExecutionError extends Error {
	constructor(
		message: string,
		public readonly details?: any,
	) {
		super(message);
		this.name = 'WslExecutionError';
	}
}

export class CommandValidationError extends WslExecutionError {
	constructor(message: string, details?: any) {
		super(message, details);
		this.name = 'CommandValidationError';
	}
}

export class CommandTimeoutError extends WslExecutionError {
	constructor(timeout: number) {
		super(`Command timed out after ${timeout}ms`, { timeout });
		this.name = 'CommandTimeoutError';
	}
}

export class InvalidConfirmationError extends WslExecutionError {
	constructor(confirmation_id: string) {
		super('Invalid or expired confirmation ID', { confirmation_id });
		this.name = 'InvalidConfirmationError';
	}
}

```

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

```json
{
	"name": "mcp-wsl-exec",
	"version": "0.0.3",
	"description": "A secure Model Context Protocol (MCP) server for executing commands in Windows Subsystem for Linux (WSL) with built-in safety features and validation",
	"type": "module",
	"main": "dist/index.js",
	"types": "dist/index.d.ts",
	"bin": {
		"mcp-wsl-exec": "./dist/index.js"
	},
	"files": [
		"dist",
		"README.md",
		"LICENSE"
	],
	"scripts": {
		"build": "tsc && chmod +x dist/index.js",
		"start": "node dist/index.js",
		"dev": "npx @modelcontextprotocol/inspector dist/index.js",
		"changeset": "changeset",
		"version": "changeset version",
		"release": "pnpm run build && changeset publish"
	},
	"keywords": [
		"mcp",
		"model-context-protocol",
		"wsl",
		"exec",
		"command-execution",
		"windows-subsystem-linux",
		"security",
		"command-line",
		"cli",
		"shell",
		"bash",
		"linux",
		"windows",
		"safe-execution",
		"command-validation",
		"path-validation",
		"timeout",
		"error-handling"
	],
	"author": "Scott Spence",
	"license": "MIT",
	"repository": {
		"type": "git",
		"url": "https://github.com/spences10/mcp-wsl-exec.git"
	},
	"bugs": {
		"url": "https://github.com/spences10/mcp-wsl-exec/issues"
	},
	"homepage": "https://github.com/spences10/mcp-wsl-exec#readme",
	"devDependencies": {
		"@changesets/cli": "^2.29.7",
		"@types/node": "^24.6.1",
		"typescript": "^5.9.3"
	},
	"dependencies": {
		"@tmcp/adapter-valibot": "^0.1.4",
		"@tmcp/transport-stdio": "^0.3.1",
		"tmcp": "^1.14.0",
		"valibot": "^1.1.0"
	}
}
```

--------------------------------------------------------------------------------
/src/command-executor.ts:
--------------------------------------------------------------------------------

```typescript
import { spawn } from 'child_process';
import { dangerous_commands, wsl_config } from './constants.js';
import { CommandValidationError, CommandTimeoutError } from './errors.js';
import { CommandResponse } from './types.js';

export class CommandExecutor {
	private sanitize_command(command: string): string {
		// Enhanced command sanitization
		const sanitized = command
			.replace(/[;&|`$]/g, '') // Remove shell metacharacters
			.replace(/\\/g, '/') // Normalize path separators
			.replace(/\.\./g, '') // Remove parent directory references
			.replace(/~/g, '') // Remove home directory references
			.trim(); // Remove leading/trailing whitespace

		// Check for empty command after sanitization
		if (!sanitized) {
			throw new CommandValidationError(
				'Invalid command: Empty after sanitization',
			);
		}

		return sanitized;
	}

	private validate_working_dir(working_dir?: string): string | undefined {
		if (!working_dir) return undefined;

		// Sanitize and validate working directory
		const sanitized = working_dir
			.replace(/[;&|`$]/g, '')
			.replace(/\\/g, '/')
			.trim();

		if (!sanitized) {
			throw new CommandValidationError('Invalid working directory');
		}

		return sanitized;
	}

	private validate_timeout(timeout?: number): number | undefined {
		if (!timeout) return undefined;

		if (isNaN(timeout) || timeout < 0) {
			throw new CommandValidationError('Invalid timeout value');
		}

		return timeout;
	}

	public is_dangerous_command(command: string): boolean {
		return dangerous_commands.some(
			(dangerous) =>
				command.toLowerCase().includes(dangerous.toLowerCase()) ||
				command.match(new RegExp(`\\b${dangerous}\\b`, 'i')),
		);
	}

	public async execute_command(
		command: string,
		working_dir?: string,
		timeout?: number,
	): Promise<CommandResponse> {
		return new Promise((resolve, reject) => {
			const sanitized_command = this.sanitize_command(command);
			const validated_dir = this.validate_working_dir(working_dir);
			const validated_timeout = this.validate_timeout(timeout);

			const cd_command = validated_dir ? `cd "${validated_dir}" && ` : '';
			const full_command = `${cd_command}${sanitized_command}`;

			const wsl_process = spawn(wsl_config.executable, [
				'--exec',
				wsl_config.shell,
				'-c',
				full_command,
			]);

			let stdout = '';
			let stderr = '';

			wsl_process.stdout.on('data', (data) => {
				stdout += data.toString();
			});

			wsl_process.stderr.on('data', (data) => {
				stderr += data.toString();
			});

			let timeout_id: NodeJS.Timeout | undefined;
			if (validated_timeout) {
				timeout_id = setTimeout(() => {
					wsl_process.kill();
					reject(new CommandTimeoutError(validated_timeout));
				}, validated_timeout);
			}

			wsl_process.on('close', (code) => {
				if (timeout_id) {
					clearTimeout(timeout_id);
				}
				resolve({
					stdout,
					stderr,
					exit_code: code,
					command: sanitized_command,
					working_dir: validated_dir,
				});
			});

			wsl_process.on('error', (error) => {
				if (timeout_id) {
					clearTimeout(timeout_id);
				}
				reject(error);
			});
		});
	}
}

```

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

```typescript
#!/usr/bin/env node

import { McpServer } from 'tmcp';
import { ValibotJsonSchemaAdapter } from '@tmcp/adapter-valibot';
import { StdioTransport } from '@tmcp/transport-stdio';
import * as v from 'valibot';
import type { GenericSchema } from 'valibot';
import { readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { CommandExecutor } from './command-executor.js';
import { InvalidConfirmationError } from './errors.js';
import { CommandResponse, PendingConfirmation } from './types.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const pkg = JSON.parse(
	readFileSync(join(__dirname, '..', 'package.json'), 'utf8'),
);
const { name, version } = pkg;

class WslServer {
	private server: McpServer<GenericSchema>;
	private command_executor: CommandExecutor;
	private pending_confirmations: Map<string, PendingConfirmation>;

	constructor() {
		const adapter = new ValibotJsonSchemaAdapter();
		this.server = new McpServer<GenericSchema>(
			{
				name,
				version,
				description: 'A secure MCP server for executing commands in WSL with built-in safety features',
			},
			{
				adapter,
				capabilities: {
					tools: { listChanged: true },
				},
			},
		);
		this.command_executor = new CommandExecutor();
		this.pending_confirmations = new Map();
		this.setup_tool_handlers();
	}

	private format_output(result: CommandResponse): string {
		return [
			`Command: ${result.command}`,
			result.working_dir
				? `Working Directory: ${result.working_dir}`
				: null,
			`Exit Code: ${result.exit_code}`,
			result.stdout.trim()
				? `Output:\n${result.stdout.trim()}`
				: 'No output',
			result.stderr.trim()
				? `Errors:\n${result.stderr.trim()}`
				: 'No errors',
			result.error ? `Error: ${result.error}` : null,
		]
			.filter(Boolean)
			.join('\n');
	}

	private async execute_wsl_command(
		command: string,
		working_dir?: string,
		timeout?: number,
	): Promise<CommandResponse> {
		return new Promise((resolve, reject) => {
			const requires_confirmation =
				this.command_executor.is_dangerous_command(command);

			if (requires_confirmation) {
				// Generate a unique confirmation ID
				const confirmation_id = Math.random()
					.toString(36)
					.substring(7);
				this.pending_confirmations.set(confirmation_id, {
					command,
					working_dir,
					timeout,
					resolve,
					reject,
				});

				// Return early with confirmation request
				resolve({
					stdout: '',
					stderr: `Command "${command}" requires confirmation. Use confirm_command with ID: ${confirmation_id}`,
					exit_code: null,
					command,
					requires_confirmation: true,
				});
				return;
			}

			this.command_executor
				.execute_command(command, working_dir, timeout)
				.then(resolve)
				.catch(reject);
		});
	}

	private setup_tool_handlers() {
		// get_system_info tool - read-only
		this.server.tool(
			{
				name: 'get_system_info',
				description: 'Get WSL system information',
				annotations: {
					readOnlyHint: true,
				},
			},
			async () => {
				try {
					const result = await this.command_executor.execute_command(
						'uname -a && lsb_release -a 2>/dev/null || cat /etc/os-release',
					);
					return {
						content: [
							{
								type: 'text' as const,
								text: this.format_output(result),
							},
						],
					};
				} catch (error) {
					return {
						content: [
							{
								type: 'text' as const,
								text: `Error: ${error instanceof Error ? error.message : String(error)}`,
							},
						],
						isError: true,
					};
				}
			},
		);

		// get_environment tool - read-only
		this.server.tool(
			{
				name: 'get_environment',
				description: 'Get WSL environment variables',
				schema: v.object({
					filter: v.optional(
						v.pipe(
							v.string(),
							v.description('Filter pattern (grep)'),
						),
					),
				}),
				annotations: {
					readOnlyHint: true,
				},
			},
			async ({ filter }) => {
				try {
					const cmd = filter ? `env | grep -i "${filter}"` : 'env';
					const result = await this.command_executor.execute_command(cmd);
					return {
						content: [
							{
								type: 'text' as const,
								text: this.format_output(result),
							},
						],
					};
				} catch (error) {
					return {
						content: [
							{
								type: 'text' as const,
								text: `Error: ${error instanceof Error ? error.message : String(error)}`,
							},
						],
						isError: true,
					};
				}
			},
		);

		// list_processes tool - read-only
		this.server.tool(
			{
				name: 'list_processes',
				description: 'List running processes in WSL',
				schema: v.object({
					filter: v.optional(
						v.pipe(
							v.string(),
							v.description('Filter by name'),
						),
					),
				}),
				annotations: {
					readOnlyHint: true,
				},
			},
			async ({ filter }) => {
				try {
					const cmd = filter
						? `ps aux | grep -i "${filter}" | grep -v grep`
						: 'ps aux';
					const result = await this.command_executor.execute_command(cmd);
					return {
						content: [
							{
								type: 'text' as const,
								text: this.format_output(result),
							},
						],
					};
				} catch (error) {
					return {
						content: [
							{
								type: 'text' as const,
								text: `Error: ${error instanceof Error ? error.message : String(error)}`,
							},
						],
						isError: true,
					};
				}
			},
		);

		// get_disk_usage tool - read-only
		this.server.tool(
			{
				name: 'get_disk_usage',
				description: 'Get disk space information',
				schema: v.object({
					path: v.optional(
						v.pipe(
							v.string(),
							v.description('Path to check'),
						),
					),
				}),
				annotations: {
					readOnlyHint: true,
				},
			},
			async ({ path }) => {
				try {
					const cmd = path ? `df -h "${path}"` : 'df -h';
					const result = await this.command_executor.execute_command(cmd);
					return {
						content: [
							{
								type: 'text' as const,
								text: this.format_output(result),
							},
						],
					};
				} catch (error) {
					return {
						content: [
							{
								type: 'text' as const,
								text: `Error: ${error instanceof Error ? error.message : String(error)}`,
							},
						],
						isError: true,
					};
				}
			},
		);

		// get_directory_info tool - read-only
		this.server.tool(
			{
				name: 'get_directory_info',
				description: 'Get directory contents and info',
				schema: v.object({
					path: v.optional(
						v.pipe(
							v.string(),
							v.description('Directory path'),
						),
					),
					details: v.optional(
						v.pipe(
							v.boolean(),
							v.description('Show detailed info'),
						),
					),
				}),
				annotations: {
					readOnlyHint: true,
				},
			},
			async ({ path, details }) => {
				try {
					const dir = path || '.';
					const cmd = details ? `ls -lah "${dir}"` : `ls -A "${dir}"`;
					const result = await this.command_executor.execute_command(cmd);
					return {
						content: [
							{
								type: 'text' as const,
								text: this.format_output(result),
							},
						],
					};
				} catch (error) {
					return {
						content: [
							{
								type: 'text' as const,
								text: `Error: ${error instanceof Error ? error.message : String(error)}`,
							},
						],
						isError: true,
					};
				}
			},
		);

		// execute_command tool - potentially destructive
		this.server.tool(
			{
				name: 'execute_command',
				description: 'Execute a command in WSL (use read-only tools when possible)',
				schema: v.object({
					command: v.pipe(
						v.string(),
						v.description('Command to execute'),
					),
					working_dir: v.optional(
						v.pipe(
							v.string(),
							v.description('Working directory'),
						),
					),
					timeout: v.optional(
						v.pipe(
							v.number(),
							v.description('Timeout (ms)'),
						),
					),
				}),
				annotations: {
					readOnlyHint: false,
					destructiveHint: true,
				},
			},
			async ({ command, working_dir, timeout }) => {
				try {
					const result = await this.execute_wsl_command(
						command,
						working_dir,
						timeout,
					);

					if (result.requires_confirmation) {
						return {
							content: [
								{
									type: 'text' as const,
									text: result.stderr,
								},
							],
						};
					}

					return {
						content: [
							{
								type: 'text' as const,
								text: this.format_output(result),
							},
						],
					};
				} catch (error) {
					return {
						content: [
							{
								type: 'text' as const,
								text: `Error executing command: ${
									error instanceof Error
										? error.message
										: String(error)
								}`,
							},
						],
						isError: true,
					};
				}
			},
		);

		// confirm_command tool
		this.server.tool(
			{
				name: 'confirm_command',
				description: 'Confirm dangerous command execution',
				schema: v.object({
					confirmation_id: v.pipe(
						v.string(),
						v.description('Confirmation ID'),
					),
					confirm: v.pipe(
						v.boolean(),
						v.description('Proceed with execution'),
					),
				}),
				annotations: {
					readOnlyHint: false,
					destructiveHint: true,
				},
			},
			async ({ confirmation_id, confirm }) => {
				try {
					const pending = this.pending_confirmations.get(confirmation_id);
					if (!pending) {
						throw new InvalidConfirmationError(confirmation_id);
					}

					this.pending_confirmations.delete(confirmation_id);

					if (!confirm) {
						return {
							content: [
								{
									type: 'text' as const,
									text: 'Command execution cancelled.',
								},
							],
						};
					}

					const result = await this.command_executor.execute_command(
						pending.command,
						pending.working_dir,
						pending.timeout,
					);

					return {
						content: [
							{
								type: 'text' as const,
								text: this.format_output(result),
							},
						],
					};
				} catch (error) {
					return {
						content: [
							{
								type: 'text' as const,
								text: `Error confirming command: ${
									error instanceof Error
										? error.message
										: String(error)
								}`,
							},
						],
						isError: true,
					};
				}
			},
		);
	}

	async run() {
		const transport = new StdioTransport(this.server);
		transport.listen();
		console.error('WSL MCP server running on stdio');
	}
}

const server = new WslServer();
server.run().catch(console.error);

```