Skip to content

Commit cd2bf42

Browse files
committed
Add Linux and macOS support (experimental)
1 parent b90feda commit cd2bf42

11 files changed

Lines changed: 650 additions & 55 deletions

File tree

.github/workflows/macos-test.yml

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
name: macOS Test
2+
3+
on:
4+
workflow_dispatch: # Manual trigger
5+
6+
jobs:
7+
test-macos:
8+
runs-on: macos-14 # M1 Mac (arm64)
9+
10+
steps:
11+
- uses: actions/checkout@v4
12+
13+
- name: Setup .NET
14+
uses: actions/setup-dotnet@v4
15+
with:
16+
dotnet-version: '9.0.x'
17+
18+
- name: Install PowerShell
19+
run: |
20+
brew install powershell/tap/powershell
21+
pwsh --version
22+
23+
- name: Build PowerShell.MCP module
24+
run: |
25+
dotnet build PowerShell.MCP -c Release --no-incremental
26+
27+
- name: Build Proxy (osx-arm64)
28+
run: |
29+
dotnet publish PowerShell.MCP.Proxy -c Release -r osx-arm64 --self-contained
30+
31+
- name: Setup module directory
32+
run: |
33+
MODULE_PATH="$HOME/.local/share/powershell/Modules/PowerShell.MCP"
34+
mkdir -p "$MODULE_PATH/bin/osx-arm64"
35+
36+
# Copy DLLs
37+
cp PowerShell.MCP/bin/Release/net9.0/PowerShell.MCP.dll "$MODULE_PATH/"
38+
cp PowerShell.MCP/bin/Release/net9.0/Ude.NetStandard.dll "$MODULE_PATH/"
39+
40+
# Copy manifest and script
41+
cp Staging/PowerShell.MCP.psd1 "$MODULE_PATH/"
42+
cp Staging/PowerShell.MCP.psm1 "$MODULE_PATH/"
43+
44+
# Copy Proxy
45+
cp PowerShell.MCP.Proxy/bin/Release/net9.0/osx-arm64/publish/PowerShell.MCP.Proxy "$MODULE_PATH/bin/osx-arm64/"
46+
chmod +x "$MODULE_PATH/bin/osx-arm64/PowerShell.MCP.Proxy"
47+
48+
echo "Module files:"
49+
ls -la "$MODULE_PATH/"
50+
ls -la "$MODULE_PATH/bin/osx-arm64/"
51+
52+
- name: Test module import
53+
run: |
54+
pwsh -NoProfile -Command '
55+
$ErrorActionPreference = "Stop"
56+
57+
Write-Host "=== Importing module ===" -ForegroundColor Cyan
58+
Import-Module PowerShell.MCP -Verbose
59+
60+
Write-Host "`n=== Module info ===" -ForegroundColor Cyan
61+
Get-Module PowerShell.MCP | Format-List Name, Version, ModuleBase
62+
63+
Write-Host "`n=== Get-MCPProxyPath ===" -ForegroundColor Cyan
64+
$proxyPath = Get-MCPProxyPath
65+
Write-Host "Proxy path: $proxyPath"
66+
if (-not (Test-Path $proxyPath)) { throw "Proxy not found at $proxyPath" }
67+
68+
Write-Host "`n=== PSReadLine status ===" -ForegroundColor Cyan
69+
$psrl = Get-Module PSReadLine
70+
if ($psrl) {
71+
Write-Host "PSReadLine is loaded (unexpected on macOS)" -ForegroundColor Yellow
72+
} else {
73+
Write-Host "PSReadLine is NOT loaded (expected on macOS)" -ForegroundColor Green
74+
}
75+
76+
Write-Host "`n=== All tests passed ===" -ForegroundColor Green
77+
'
78+
79+
- name: Test Proxy and Named Pipe communication
80+
run: |
81+
pwsh -NoProfile -Command '
82+
$ErrorActionPreference = "Stop"
83+
84+
Write-Host "=== Starting Proxy ===" -ForegroundColor Cyan
85+
$proxyPath = Get-MCPProxyPath
86+
87+
# Start Proxy in background
88+
$proxy = Start-Process -FilePath $proxyPath -PassThru -RedirectStandardInput proxy_stdin.txt -RedirectStandardOutput proxy_stdout.txt -RedirectStandardError proxy_stderr.txt
89+
90+
Start-Sleep -Seconds 2
91+
92+
Write-Host "Proxy PID: $($proxy.Id)"
93+
94+
# Send initialize request
95+
$initRequest = ''{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}''
96+
$initRequest | Out-File -FilePath proxy_stdin.txt -Encoding utf8
97+
98+
Start-Sleep -Seconds 2
99+
100+
Write-Host "`n=== Proxy stderr ===" -ForegroundColor Yellow
101+
if (Test-Path proxy_stderr.txt) { Get-Content proxy_stderr.txt }
102+
103+
Write-Host "`n=== Proxy stdout ===" -ForegroundColor Yellow
104+
if (Test-Path proxy_stdout.txt) { Get-Content proxy_stdout.txt }
105+
106+
# Cleanup
107+
Stop-Process -Id $proxy.Id -Force -ErrorAction SilentlyContinue
108+
109+
Write-Host "`n=== Proxy test completed ===" -ForegroundColor Green
110+
'

Build-AllPlatforms.ps1

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Build-AllPlatforms.ps1
2+
# Builds PowerShell.MCP module and PowerShell.MCP.Proxy for all supported platforms
3+
4+
[CmdletBinding()]
5+
param(
6+
[string]$Configuration = 'Release',
7+
[string]$OutputBase
8+
)
9+
10+
$ErrorActionPreference = 'Stop'
11+
12+
# Determine output base from installed module if not specified
13+
if (-not $OutputBase) {
14+
$module = Get-Module PowerShell.MCP -ListAvailable | Select-Object -First 1
15+
if (-not $module) {
16+
Write-Error "PowerShell.MCP module not found. Please install the module first or specify -OutputBase parameter."
17+
exit 1
18+
}
19+
$OutputBase = $module.ModuleBase
20+
Write-Host "Detected module path: $OutputBase" -ForegroundColor Gray
21+
}
22+
23+
$moduleProjectPath = Join-Path $PSScriptRoot 'PowerShell.MCP'
24+
$proxyProjectPath = Join-Path $PSScriptRoot 'PowerShell.MCP.Proxy'
25+
$stagingPath = Join-Path $PSScriptRoot 'Staging'
26+
$helpSourcePath = Join-Path $PSScriptRoot 'PowerShell.MCP\PlatyPS\en-US'
27+
28+
Write-Host "========================================" -ForegroundColor Cyan
29+
Write-Host "PowerShell.MCP Build Script" -ForegroundColor Cyan
30+
Write-Host "Output: $OutputBase" -ForegroundColor Cyan
31+
Write-Host "========================================" -ForegroundColor Cyan
32+
Write-Host ""
33+
34+
# =============================================================================
35+
# Step 1: Build PowerShell.MCP.dll
36+
# =============================================================================
37+
Write-Host "[1/4] Building PowerShell.MCP.dll..." -ForegroundColor Yellow
38+
39+
$buildArgs = @(
40+
'build'
41+
$moduleProjectPath
42+
'-c', $Configuration
43+
'--no-incremental'
44+
'--source', 'https://api.nuget.org/v3/index.json'
45+
)
46+
47+
& dotnet @buildArgs
48+
49+
if ($LASTEXITCODE -ne 0) {
50+
Write-Error "PowerShell.MCP.dll build failed!"
51+
exit 1
52+
}
53+
54+
Write-Host " Build completed" -ForegroundColor Green
55+
Write-Host ""
56+
57+
# =============================================================================
58+
# Step 2: Copy required files to OutputBase
59+
# =============================================================================
60+
Write-Host "[2/4] Copying module files..." -ForegroundColor Yellow
61+
62+
# Source paths
63+
$buildOutputPath = Join-Path $moduleProjectPath "bin\$Configuration\net9.0"
64+
65+
# Copy DLLs from build output
66+
Copy-Item (Join-Path $buildOutputPath 'PowerShell.MCP.dll') -Destination $OutputBase -Force
67+
Copy-Item (Join-Path $buildOutputPath 'Ude.NetStandard.dll') -Destination $OutputBase -Force
68+
Write-Host " Copied: PowerShell.MCP.dll" -ForegroundColor Green
69+
Write-Host " Copied: Ude.NetStandard.dll" -ForegroundColor Green
70+
71+
# Copy manifest and script from Staging
72+
Copy-Item (Join-Path $stagingPath 'PowerShell.MCP.psd1') -Destination $OutputBase -Force
73+
Copy-Item (Join-Path $stagingPath 'PowerShell.MCP.psm1') -Destination $OutputBase -Force
74+
Write-Host " Copied: PowerShell.MCP.psd1" -ForegroundColor Green
75+
Write-Host " Copied: PowerShell.MCP.psm1" -ForegroundColor Green
76+
77+
# Copy help file
78+
$helpDestPath = Join-Path $OutputBase 'en-US'
79+
if (-not (Test-Path $helpDestPath)) {
80+
New-Item -Path $helpDestPath -ItemType Directory -Force | Out-Null
81+
}
82+
$helpFile = Join-Path $helpSourcePath 'PowerShell.MCP.dll-Help.xml'
83+
if (Test-Path $helpFile) {
84+
Copy-Item $helpFile -Destination $helpDestPath -Force
85+
Write-Host " Copied: en-US\PowerShell.MCP.dll-Help.xml" -ForegroundColor Green
86+
} else {
87+
Write-Warning " Help file not found: $helpFile"
88+
}
89+
90+
Write-Host ""
91+
92+
# =============================================================================
93+
# Step 3: Build PowerShell.MCP.Proxy for all platforms
94+
# =============================================================================
95+
Write-Host "[3/4] Building PowerShell.MCP.Proxy for all platforms..." -ForegroundColor Yellow
96+
97+
$rids = @(
98+
'win-x64',
99+
'linux-x64',
100+
'osx-x64',
101+
'osx-arm64'
102+
)
103+
104+
$binBase = Join-Path $OutputBase 'bin'
105+
106+
foreach ($rid in $rids) {
107+
Write-Host " [$rid] Publishing..." -ForegroundColor Gray
108+
109+
$outputDir = Join-Path $binBase $rid
110+
111+
if (-not (Test-Path $outputDir)) {
112+
New-Item -Path $outputDir -ItemType Directory -Force | Out-Null
113+
}
114+
115+
$publishArgs = @(
116+
'publish'
117+
$proxyProjectPath
118+
'-c', $Configuration
119+
'-r', $rid
120+
'-o', $outputDir
121+
'--source', 'https://api.nuget.org/v3/index.json'
122+
)
123+
124+
& dotnet @publishArgs
125+
126+
if ($LASTEXITCODE -ne 0) {
127+
Write-Error "[$rid] Build failed!"
128+
exit 1
129+
}
130+
131+
$exeName = if ($rid -like 'win-*') { 'PowerShell.MCP.Proxy.exe' } else { 'PowerShell.MCP.Proxy' }
132+
$exePath = Join-Path $outputDir $exeName
133+
134+
if (Test-Path $exePath) {
135+
$size = [math]::Round((Get-Item $exePath).Length / 1MB, 2)
136+
Write-Host " [$rid] Success ($size MB)" -ForegroundColor Green
137+
} else {
138+
Write-Warning " [$rid] Executable not found at expected path"
139+
}
140+
}
141+
142+
Write-Host ""
143+
144+
# =============================================================================
145+
# Step 4: Summary
146+
# =============================================================================
147+
Write-Host "[4/4] Verifying output..." -ForegroundColor Yellow
148+
149+
$expectedFiles = @(
150+
'PowerShell.MCP.dll',
151+
'PowerShell.MCP.psd1',
152+
'PowerShell.MCP.psm1',
153+
'Ude.NetStandard.dll',
154+
'en-US\PowerShell.MCP.dll-Help.xml',
155+
'bin\win-x64\PowerShell.MCP.Proxy.exe',
156+
'bin\linux-x64\PowerShell.MCP.Proxy',
157+
'bin\osx-x64\PowerShell.MCP.Proxy',
158+
'bin\osx-arm64\PowerShell.MCP.Proxy'
159+
)
160+
161+
$allPresent = $true
162+
foreach ($file in $expectedFiles) {
163+
$path = Join-Path $OutputBase $file
164+
if (-not (Test-Path $path)) {
165+
Write-Warning " Missing: $file"
166+
$allPresent = $false
167+
}
168+
}
169+
170+
if ($allPresent) {
171+
Write-Host " All required files present" -ForegroundColor Green
172+
}
173+
174+
Write-Host ""
175+
Write-Host "========================================" -ForegroundColor Cyan
176+
Write-Host "Build completed successfully!" -ForegroundColor Cyan
177+
Write-Host "========================================" -ForegroundColor Cyan
178+
Write-Host ""
179+
Write-Host "Verify with: Get-MCPProxyPath" -ForegroundColor Gray

PowerShell.MCP.Proxy/PowerShell.MCP.Proxy.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
<!-- Single File デプロイ設定 -->
1111
<PublishSingleFile>true</PublishSingleFile>
1212
<SelfContained>true</SelfContained>
13-
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
1413

1514
<!-- ファイルサイズ最適化設定 -->
1615
<PublishTrimmed>true</PublishTrimmed>

PowerShell.MCP.Proxy/Services/NamedPipeClient.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public async Task<string> SendRequestAsync(string arguments)
2525
}
2626
catch (TimeoutException)
2727
{
28-
return $"PowerShell 7 (pwsh.exe) is running but PowerShell.MCP module is not imported.\r\n\r\nMANDATORY STEPS - DO NOT SKIP ANY:\r\n1. Explain the situation to the user\r\n2. Present exactly these two options:\r\n - Option 1: User imports PowerShell.MCP module manually in current console\r\n - Option 2: I will automatically start fresh console with PowerShell.MCP imported\r\n3. STOP and wait for explicit user response - DO NOT make any choice for the user\r\n4. ONLY if user explicitly chooses option 2, execute start_powershell_console\r\n5. If user chooses option 1, provide the command: Import-Module PowerShell.MCP\r\n6. DO NOT execute any PowerShell commands until user makes their choice\r\n\r\nCRITICAL: Never assume user preference or execute start_powershell_console without explicit user consent.";
28+
return $"PowerShell 7 (pwsh) is running but PowerShell.MCP module is not imported.\r\n\r\nMANDATORY STEPS - DO NOT SKIP ANY:\r\n1. Explain the situation to the user\r\n2. Present exactly these two options:\r\n - Option 1: User imports PowerShell.MCP module manually in current console\r\n - Option 2: I will automatically start fresh console with PowerShell.MCP imported\r\n3. STOP and wait for explicit user response - DO NOT make any choice for the user\r\n4. ONLY if user explicitly chooses option 2, execute start_powershell_console\r\n5. If user chooses option 1, provide the command: Import-Module PowerShell.MCP\r\n6. DO NOT execute any PowerShell commands until user makes their choice\r\n\r\nCRITICAL: Never assume user preference or execute start_powershell_console without explicit user consent.";
2929
}
3030

3131
// Convert JSON message to UTF-8 bytes
@@ -108,6 +108,8 @@ public static async Task<bool> WaitForPipeReadyAsync()
108108
using var testClient = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut);
109109
await testClient.ConnectAsync(500); // 500ms timeout
110110
Console.Error.WriteLine($"[INFO] Named Pipe ready after {attempt} attempts");
111+
// Give the server time to prepare for the next connection
112+
await Task.Delay(500);
111113
return true;
112114
}
113115
catch (TimeoutException)

0 commit comments

Comments
 (0)