Skip to content

Commit 1f95db8

Browse files
Enhance MCP commands for WSL compatibility (#121)
* Enhance MCP commands for WSL compatibility * Add optional parameter to `getPhpPath` for absolute path control * feat: add tests for WSL and absolute path overrides * refactor: simplify 'isRunningInWsl' * refactor: reduce conditional on boost mcp install * rector: update test return types --------- Co-authored-by: Ashley Hindle <[email protected]>
1 parent 2687e0c commit 1f95db8

File tree

6 files changed

+130
-10
lines changed

6 files changed

+130
-10
lines changed

src/Console/InstallCommand.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -487,11 +487,21 @@ private function installMcpServerConfig(): void
487487
$this->output->write(" {$ideDisplay}... ");
488488
$results = [];
489489

490-
$php = $mcpClient->getPhpPath();
491490
if ($this->shouldInstallMcp()) {
491+
$inWsl = $this->isRunningInWsl();
492+
$mcp = array_filter([
493+
'laravel-boost',
494+
$inWsl ? 'wsl' : false,
495+
$mcpClient->getPhpPath($inWsl),
496+
$mcpClient->getArtisanPath($inWsl),
497+
'boost:mcp',
498+
]);
492499
try {
493-
$artisan = $mcpClient->getArtisanPath();
494-
$result = $mcpClient->installMcp('laravel-boost', $php, [$artisan, 'boost:mcp']);
500+
$result = $mcpClient->installMcp(
501+
array_shift($mcp),
502+
array_shift($mcp),
503+
$mcp
504+
);
495505

496506
if ($result) {
497507
$results[] = $this->greenTick.' Boost';
@@ -507,6 +517,7 @@ private function installMcpServerConfig(): void
507517

508518
// Install Herd MCP if enabled
509519
if ($this->shouldInstallHerdMcp()) {
520+
$php = $mcpClient->getPhpPath();
510521
try {
511522
$result = $mcpClient->installMcp(
512523
key: 'herd',
@@ -552,4 +563,14 @@ private function detectLocalization(): bool
552563
/** @phpstan-ignore-next-line */
553564
return $actuallyUsing && is_dir(base_path('lang'));
554565
}
566+
567+
/**
568+
* Are we running inside a Windows Subsystem for Linux (WSL) environment?
569+
* This differentiates between a regular Linux installation and a WSL.
570+
*/
571+
private function isRunningInWsl(): bool
572+
{
573+
// Check for WSL-specific environment variables.
574+
return ! empty(getenv('WSL_DISTRO_NAME')) || ! empty(getenv('IS_WSL'));
575+
}
555576
}

src/Contracts/McpClient.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ public function useAbsolutePathForMcp(): bool;
2222
/**
2323
* Get the PHP executable path for this MCP client.
2424
*/
25-
public function getPhpPath(): string;
25+
public function getPhpPath(bool $forceAbsolutePath = false): string;
2626

2727
/**
2828
* Get the artisan path for this MCP client.
2929
*/
30-
public function getArtisanPath(): string;
30+
public function getArtisanPath(bool $forceAbsolutePath = false): string;
3131

3232
/**
3333
* Install an MCP server configuration in this IDE.

src/Install/CodeEnvironment/CodeEnvironment.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace Laravel\Boost\Install\CodeEnvironment;
66

77
use Illuminate\Contracts\Filesystem\FileNotFoundException;
8-
use Illuminate\Support\Facades\File;
98
use Illuminate\Support\Facades\Process;
109
use Laravel\Boost\Contracts\Agent;
1110
use Laravel\Boost\Contracts\McpClient;
@@ -39,14 +38,14 @@ public function useAbsolutePathForMcp(): bool
3938
return $this->useAbsolutePathForMcp;
4039
}
4140

42-
public function getPhpPath(): string
41+
public function getPhpPath(bool $forceAbsolutePath = false): string
4342
{
44-
return $this->useAbsolutePathForMcp() ? PHP_BINARY : 'php';
43+
return ($this->useAbsolutePathForMcp() || $forceAbsolutePath) ? PHP_BINARY : 'php';
4544
}
4645

47-
public function getArtisanPath(): string
46+
public function getArtisanPath(bool $forceAbsolutePath = false): string
4847
{
49-
return $this->useAbsolutePathForMcp() ? base_path('artisan') : 'artisan';
48+
return ($this->useAbsolutePathForMcp() || $forceAbsolutePath) ? base_path('artisan') : 'artisan';
5049
}
5150

5251
/**
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Laravel\Boost\Console\InstallCommand;
6+
7+
test('isRunningInWsl returns true when WSL_DISTRO_NAME is set', function (): void {
8+
putenv('WSL_DISTRO_NAME=Ubuntu');
9+
10+
$command = new InstallCommand;
11+
$reflection = new ReflectionClass($command);
12+
$method = $reflection->getMethod('isRunningInWsl');
13+
14+
expect($method->invoke($command))->toBeTrue();
15+
});
16+
17+
test('isRunningInWsl returns true when IS_WSL is set', function (): void {
18+
putenv('IS_WSL=1');
19+
20+
$command = new InstallCommand;
21+
$reflection = new ReflectionClass($command);
22+
$method = $reflection->getMethod('isRunningInWsl');
23+
24+
expect($method->invoke($command))->toBeTrue();
25+
});
26+
27+
test('isRunningInWsl returns true when both WSL env vars are set', function (): void {
28+
putenv('WSL_DISTRO_NAME=Ubuntu');
29+
putenv('IS_WSL=true');
30+
31+
$command = new InstallCommand;
32+
$reflection = new ReflectionClass($command);
33+
$method = $reflection->getMethod('isRunningInWsl');
34+
35+
expect($method->invoke($command))->toBeTrue();
36+
});
37+
38+
test('isRunningInWsl returns false when no WSL env vars are set', function (): void {
39+
putenv('WSL_DISTRO_NAME');
40+
putenv('IS_WSL');
41+
42+
$command = new InstallCommand;
43+
$reflection = new ReflectionClass($command);
44+
$method = $reflection->getMethod('isRunningInWsl');
45+
46+
expect($method->invoke($command))->toBeFalse();
47+
});
48+
49+
test('isRunningInWsl returns false when WSL env vars are empty strings', function (): void {
50+
putenv('WSL_DISTRO_NAME=');
51+
putenv('IS_WSL=');
52+
53+
$command = new InstallCommand;
54+
$reflection = new ReflectionClass($command);
55+
$method = $reflection->getMethod('isRunningInWsl');
56+
57+
expect($method->invoke($command))->toBeFalse();
58+
});

tests/Feature/Install/CodeEnvironment/CodeEnvironmentPathResolutionTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,35 @@
3737

3838
expect($cursor->getArtisanPath())->toBe('artisan');
3939
});
40+
41+
test('CodeEnvironment returns absolute paths when forceAbsolutePath is true', function (): void {
42+
$strategyFactory = Mockery::mock(DetectionStrategyFactory::class);
43+
$cursor = new Cursor($strategyFactory);
44+
45+
expect($cursor->getPhpPath(true))->toBe(PHP_BINARY);
46+
expect($cursor->getArtisanPath(true))->toEndWith('artisan')
47+
->and($cursor->getArtisanPath(true))->not()->toBe('artisan');
48+
});
49+
50+
test('CodeEnvironment maintains relative paths when forceAbsolutePath is false', function (): void {
51+
$strategyFactory = Mockery::mock(DetectionStrategyFactory::class);
52+
$cursor = new Cursor($strategyFactory);
53+
54+
expect($cursor->getPhpPath(false))->toBe('php');
55+
expect($cursor->getArtisanPath(false))->toBe('artisan');
56+
});
57+
58+
test('PhpStorm paths remain absolute regardless of forceAbsolutePath parameter', function (): void {
59+
$strategyFactory = Mockery::mock(DetectionStrategyFactory::class);
60+
$phpStorm = new PhpStorm($strategyFactory);
61+
62+
// PhpStorm always uses absolute paths, so forceAbsolutePath shouldn't change behavior
63+
expect($phpStorm->getPhpPath(true))->toBe(PHP_BINARY);
64+
expect($phpStorm->getPhpPath(false))->toBe(PHP_BINARY);
65+
66+
$artisanPath = $phpStorm->getArtisanPath(true);
67+
expect($artisanPath)->toEndWith('artisan')
68+
->and($artisanPath)->not()->toBe('artisan');
69+
70+
expect($phpStorm->getArtisanPath(false))->toBe($artisanPath);
71+
});

tests/Unit/Install/CodeEnvironment/CodeEnvironmentTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,3 +409,13 @@ public function mcpConfigPath(): string
409409
->and($capturedPath)->toBe($vscode->mcpConfigPath())
410410
->and($capturedContent)->toBe(fixture('mcp-expected.json5'));
411411
});
412+
413+
test('getPhpPath uses absolute paths when forceAbsolutePath is true', function (): void {
414+
$environment = new TestCodeEnvironment($this->strategyFactory);
415+
expect($environment->getPhpPath(true))->toBe(PHP_BINARY);
416+
});
417+
418+
test('getPhpPath maintains default behavior when forceAbsolutePath is false', function (): void {
419+
$environment = new TestCodeEnvironment($this->strategyFactory);
420+
expect($environment->getPhpPath(false))->toBe('php');
421+
});

0 commit comments

Comments
 (0)