@@ -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+
163255if ($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.
194315if (-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