Skip to content

Commit 06f7df0

Browse files
gfraiteurclaude
andcommitted
Fix MCP server file locking and Windows Firewall prompts
- Copy MCP server files to temp directory before cleanup to avoid file locking - Use hash-based temp directory path (not random GUID) to prevent repeated Windows Firewall prompts - Build MCP server before cleanup to ensure files exist - Add graceful error handling - script continues without MCP if startup fails - Temp directory is cleaned up on both success and error paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent 56d552f commit 06f7df0

File tree

2 files changed

+394
-78
lines changed

2 files changed

+394
-78
lines changed

DockerBuild.ps1

Lines changed: 197 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,98 @@ function New-ClaudeEnvJson
160160
return $jsonPath
161161
}
162162

163+
# Function to prepare MCP server for execution by copying to temp directory
164+
# This avoids file locking issues when running the MCP server
165+
function Copy-McpServerToTemp
166+
{
167+
param(
168+
[string]$SourceRootDir
169+
)
170+
171+
# Find the BuildTools Debug directory
172+
$debugDir = Join-Path $SourceRootDir "$EngPath\src\bin\Debug"
173+
174+
if (-not (Test-Path $debugDir))
175+
{
176+
throw "MCP server Debug directory not found: $debugDir. Please build the project first using: Build.ps1"
177+
}
178+
179+
# Get the single subdirectory (e.g., net8.0, net9.0)
180+
$targetFrameworkDirs = Get-ChildItem -Path $debugDir -Directory
181+
182+
if ($targetFrameworkDirs.Count -eq 0)
183+
{
184+
throw "No target framework directory found in $debugDir. Please build the project first using: Build.ps1"
185+
}
186+
187+
if ($targetFrameworkDirs.Count -gt 1)
188+
{
189+
Write-Warning "Multiple target framework directories found in $debugDir"
190+
Write-Warning "Using the first one: $($targetFrameworkDirs[0].Name)"
191+
}
192+
193+
$targetFrameworkDir = $targetFrameworkDirs[0].FullName
194+
Write-Host "Found MCP server build directory: $targetFrameworkDir" -ForegroundColor Cyan
195+
196+
# Find the executable (.exe) or library (.dll)
197+
$exeFiles = Get-ChildItem -Path $targetFrameworkDir -Filter "*.exe"
198+
$dllFiles = Get-ChildItem -Path $targetFrameworkDir -Filter "*.dll" | Where-Object { $_.Name -notlike "*.resources.dll" }
199+
200+
$executableFile = $null
201+
if ($exeFiles.Count -gt 0)
202+
{
203+
$executableFile = $exeFiles[0]
204+
}
205+
elseif ($dllFiles.Count -gt 0)
206+
{
207+
# Prefer files with "Build" in the name
208+
$buildDll = $dllFiles | Where-Object { $_.Name -like "*Build*" } | Select-Object -First 1
209+
if ($buildDll)
210+
{
211+
$executableFile = $buildDll
212+
}
213+
else
214+
{
215+
$executableFile = $dllFiles[0]
216+
}
217+
}
218+
else
219+
{
220+
throw "No executable (.exe) or assembly (.dll) found in $targetFrameworkDir"
221+
}
222+
223+
Write-Host "Found MCP server executable: $($executableFile.Name)" -ForegroundColor Cyan
224+
225+
# Create temporary directory using hash of source directory
226+
# This ensures the same repo always uses the same temp path (avoiding firewall prompts)
227+
# but different repos won't conflict
228+
$hashBytes = (New-Object -TypeName System.Security.Cryptography.SHA256Managed).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($SourceRootDir))
229+
$directoryHash = [System.BitConverter]::ToString($hashBytes, 0, 4).Replace("-", "").ToLower()
230+
$tempDir = Join-Path $env:TEMP "mcp-server-$directoryHash"
231+
232+
# Clean up old temp directory if it exists
233+
if (Test-Path $tempDir)
234+
{
235+
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
236+
}
237+
238+
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
239+
Write-Host "Created temporary directory: $tempDir" -ForegroundColor Cyan
240+
241+
# Copy the entire target framework directory to temp
242+
$tempTargetDir = Join-Path $tempDir $targetFrameworkDirs[0].Name
243+
Copy-Item -Path $targetFrameworkDir -Destination $tempTargetDir -Recurse -Force
244+
Write-Host "Copied MCP server files to temporary directory" -ForegroundColor Cyan
245+
246+
# Return the path to the executable and the temp directory for cleanup
247+
$tempExecutable = Join-Path $tempTargetDir $executableFile.Name
248+
return @{
249+
ExecutablePath = $tempExecutable
250+
TempDirectory = $tempDir
251+
IsExe = $executableFile.Extension -eq ".exe"
252+
}
253+
}
254+
163255
if ($env:RUNNING_IN_DOCKER)
164256
{
165257
Write-Error "Already running in Docker."
@@ -189,7 +281,36 @@ else
189281
Write-Host "Image will be tagged as: $ImageTag" -ForegroundColor Cyan
190282
}
191283

192-
# When building locally (as opposed as on the build agent), we must do a complete cleanup because
284+
# Save MCP server files to temp directory BEFORE cleanup (for -Claude mode)
285+
# This must happen before cleaning because cleanup removes all bin directories
286+
$mcpServerSnapshot = $null
287+
if ($Claude -and -not $NoMcp)
288+
{
289+
try
290+
{
291+
Write-Host "Building MCP server before cleanup..." -ForegroundColor Cyan
292+
$mcpProjectPath = Join-Path $PSScriptRoot "$EngPath\src"
293+
294+
# Build the MCP server project
295+
& dotnet build $mcpProjectPath --configuration Debug --nologo --verbosity quiet
296+
if ($LASTEXITCODE -ne 0)
297+
{
298+
throw "Failed to build MCP server project at $mcpProjectPath"
299+
}
300+
301+
Write-Host "Saving MCP server files before cleanup..." -ForegroundColor Cyan
302+
$mcpServerSnapshot = Copy-McpServerToTemp -SourceRootDir $PSScriptRoot
303+
Write-Host "MCP server files saved to: $($mcpServerSnapshot.TempDirectory)" -ForegroundColor Cyan
304+
}
305+
catch
306+
{
307+
Write-Host "WARNING: Could not save MCP server files: $_" -ForegroundColor Yellow
308+
Write-Host "MCP server will not be available." -ForegroundColor Yellow
309+
$mcpServerSnapshot = $null
310+
}
311+
}
312+
313+
# When building locally (as opposed as on the build agent), we must do a complete cleanup because
193314
# obj files may point to the host filesystem.
194315
if (-not $env:IS_TEAMCITY_AGENT -and -not $NoClean)
195316
{
@@ -561,52 +682,83 @@ if (-not $BuildImage)
561682
$mcpPort = $null
562683
$mcpPortFile = $null
563684
$mcpSecret = $null
685+
$mcpTempDir = $null
564686
if (-not $NoMcp) {
565-
Write-Host "Starting MCP approval server..." -ForegroundColor Green
566-
$mcpPortFile = Join-Path $env:TEMP "mcp-port-$([System.Guid]::NewGuid().ToString('N').Substring(0,8)).txt"
567-
568-
# Generate 128-bit (16 byte) random secret for authentication
569-
$randomBytes = New-Object byte[] 16
570-
[Security.Cryptography.RandomNumberGenerator]::Fill($randomBytes)
571-
$mcpSecret = [Convert]::ToBase64String($randomBytes)
572-
Write-Host "Generated MCP authentication secret" -ForegroundColor Cyan
573-
574-
# Build the command to run in the new tab
575-
$mcpCommand = "& '$SourceDirName\Build.ps1' tools mcp-server --port-file '$mcpPortFile' --secret '$mcpSecret'"
576-
577-
# Try Windows Terminal first (wt.exe), fall back to conhost
578-
$wtPath = Get-Command wt.exe -ErrorAction SilentlyContinue
579-
if ($wtPath) {
580-
# Open new tab in current Windows Terminal window
581-
# The -w 0 option targets the current window
582-
# Use single argument string for proper escaping
583-
$wtArgString = "-w 0 new-tab --title `"MCP Approval Server`" -- pwsh -NoExit -Command `"$mcpCommand`""
584-
$mcpServerProcess = Start-Process -FilePath "wt.exe" -ArgumentList $wtArgString -PassThru
585-
} else {
586-
# Fallback: start in new console window
587-
$mcpServerProcess = Start-Process -FilePath "pwsh" `
588-
-ArgumentList "-NoExit", "-Command", $mcpCommand `
589-
-PassThru
590-
}
687+
try {
688+
# Check if MCP server snapshot was saved before cleanup
689+
if (-not $mcpServerSnapshot) {
690+
throw "MCP server files were not saved before cleanup. Cannot start MCP server."
691+
}
692+
693+
Write-Host "Starting MCP approval server..." -ForegroundColor Green
694+
$mcpPortFile = Join-Path $env:TEMP "mcp-port-$([System.Guid]::NewGuid().ToString('N').Substring(0,8)).txt"
695+
696+
# Generate 128-bit (16 byte) random secret for authentication
697+
$randomBytes = New-Object byte[] 16
698+
[Security.Cryptography.RandomNumberGenerator]::Fill($randomBytes)
699+
$mcpSecret = [Convert]::ToBase64String($randomBytes)
700+
Write-Host "Generated MCP authentication secret" -ForegroundColor Cyan
701+
702+
# Use the MCP server snapshot saved before cleanup
703+
$mcpServerInfo = $mcpServerSnapshot
704+
$mcpTempDir = $mcpServerInfo.TempDirectory
705+
706+
# Build the command to run in the new tab
707+
if ($mcpServerInfo.IsExe) {
708+
# Run executable directly
709+
$mcpCommand = "& '$($mcpServerInfo.ExecutablePath)' tools mcp-server --port-file '$mcpPortFile' --secret '$mcpSecret'"
710+
} else {
711+
# Run DLL with dotnet
712+
$mcpCommand = "dotnet '$($mcpServerInfo.ExecutablePath)' tools mcp-server --port-file '$mcpPortFile' --secret '$mcpSecret'"
713+
}
714+
715+
# Try Windows Terminal first (wt.exe), fall back to conhost
716+
$wtPath = Get-Command wt.exe -ErrorAction SilentlyContinue
717+
if ($wtPath) {
718+
# Open new tab in current Windows Terminal window
719+
# The -w 0 option targets the current window
720+
# Use single argument string for proper escaping
721+
$wtArgString = "-w 0 new-tab --title `"MCP Approval Server`" -- pwsh -NoExit -Command `"$mcpCommand`""
722+
$mcpServerProcess = Start-Process -FilePath "wt.exe" -ArgumentList $wtArgString -PassThru
723+
} else {
724+
# Fallback: start in new console window
725+
$mcpServerProcess = Start-Process -FilePath "pwsh" `
726+
-ArgumentList "-NoExit", "-Command", $mcpCommand `
727+
-PassThru
728+
}
729+
730+
# Wait for port file to be written (with timeout)
731+
$timeout = 30
732+
$elapsed = 0
733+
while (-not (Test-Path $mcpPortFile) -and $elapsed -lt $timeout) {
734+
Start-Sleep -Milliseconds 500
735+
$elapsed += 0.5
736+
}
591737

592-
# Wait for port file to be written (with timeout)
593-
$timeout = 30
594-
$elapsed = 0
595-
while (-not (Test-Path $mcpPortFile) -and $elapsed -lt $timeout) {
596-
Start-Sleep -Milliseconds 500
597-
$elapsed += 0.5
738+
if (-not (Test-Path $mcpPortFile)) {
739+
throw "MCP server failed to start within $timeout seconds"
740+
}
741+
742+
$mcpPort = (Get-Content $mcpPortFile -Raw).Trim()
743+
Write-Host "MCP approval server running on port $mcpPort" -ForegroundColor Cyan
598744
}
745+
catch {
746+
Write-Host "ERROR: Failed to start MCP approval server: $_" -ForegroundColor Red
747+
Write-Host "Continuing without MCP server support." -ForegroundColor Yellow
599748

600-
if (-not (Test-Path $mcpPortFile)) {
601-
Write-Error "MCP server failed to start within $timeout seconds"
749+
# Clean up on error
602750
if ($mcpServerProcess -and !$mcpServerProcess.HasExited) {
603751
Stop-Process -Id $mcpServerProcess.Id -Force -ErrorAction SilentlyContinue
604752
}
605-
exit 1
606-
}
753+
if ($mcpTempDir -and (Test-Path $mcpTempDir)) {
754+
Remove-Item $mcpTempDir -Recurse -Force -ErrorAction SilentlyContinue
755+
}
607756

608-
$mcpPort = (Get-Content $mcpPortFile -Raw).Trim()
609-
Write-Host "MCP approval server running on port $mcpPort" -ForegroundColor Cyan
757+
# Reset variables to continue without MCP
758+
$mcpPort = $null
759+
$mcpSecret = $null
760+
$mcpTempDir = $null
761+
}
610762
} else {
611763
Write-Host "Skipping MCP approval server (-NoMcp specified)." -ForegroundColor Yellow
612764
}
@@ -733,6 +885,12 @@ if (-not $BuildImage)
733885
if ($mcpPortFile -and (Test-Path $mcpPortFile)) {
734886
Remove-Item $mcpPortFile -ErrorAction SilentlyContinue
735887
}
888+
889+
# Clean up temporary MCP server directory
890+
if ($mcpTempDir -and (Test-Path $mcpTempDir)) {
891+
Write-Host "Cleaning up temporary MCP server directory: $mcpTempDir" -ForegroundColor Cyan
892+
Remove-Item $mcpTempDir -Recurse -Force -ErrorAction SilentlyContinue
893+
}
736894
}
737895

738896
if ($dockerExitCode -ne 0)

0 commit comments

Comments
 (0)