2828
2929# ###
3030# These settings are replaced by the generate-scripts command.
31- $EngPath = ' eng '
32- $EnvironmentVariables = ' AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AZ_IDENTITY_USERNAME,AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,AZURE_DEVOPS_TOKEN,AZURE_DEVOPS_USER,AZURE_TENANT_ID,DOC_API_KEY,DOWNLOADS_API_KEY,ENG_USERNAME,GIT_USER_EMAIL,GIT_USER_NAME,GITHUB_AUTHOR_EMAIL,GITHUB_REVIEWER_TOKEN,GITHUB_TOKEN,IS_POSTSHARP_OWNED,IS_TEAMCITY_AGENT,MetalamaLicense,NUGET_ORG_API_KEY,PostSharpLicense,SIGNSERVER_SECRET,TEAMCITY_CLOUD_TOKEN,TYPESENSE_API_KEY,VS_MARKETPLACE_ACCESS_TOKEN,VSS_NUGET_EXTERNAL_FEED_ENDPOINTS '
31+ $EngPath = ' <ENG_PATH> '
32+ $EnvironmentVariables = ' <ENVIRONMENT_VARIABLES> '
3333# ###
3434
3535$ErrorActionPreference = " Stop"
@@ -335,9 +335,32 @@ function Copy-McpServerToTemp
335335 $hashBytes = (New-Object - TypeName System.Security.Cryptography.SHA256Managed).ComputeHash([System.Text.Encoding ]::UTF8.GetBytes($SourceRootDir ))
336336 $directoryHash = [System.BitConverter ]::ToString($hashBytes , 0 , 4 ).Replace(" -" , " " ).ToLower()
337337 $tempDir = Join-Path $env: TEMP " mcp-server-$directoryHash "
338+ $mcpPidFile = Join-Path $env: TEMP " mcp-pid-$directoryHash .txt"
339+
340+ # Kill existing MCP server for this repo if running
341+ if (Test-Path $mcpPidFile )
342+ {
343+ try
344+ {
345+ $existingPid = (Get-Content $mcpPidFile - Raw).Trim()
346+ if ($existingPid -match ' ^\d+$' )
347+ {
348+ $process = Get-Process - Id $existingPid - ErrorAction SilentlyContinue
349+ if ($process )
350+ {
351+ Write-Host " Killing existing MCP server (PID: $existingPid )..." - ForegroundColor Yellow
352+ Stop-Process - Id $existingPid - Force - ErrorAction SilentlyContinue
353+ Start-Sleep - Milliseconds 1000
354+ }
355+ }
356+ }
357+ catch
358+ {
359+ Write-Host " Could not kill existing MCP server: $_ " - ForegroundColor Yellow
360+ }
361+ }
338362
339363 # Clean up old temp directory if it exists
340- # Use retry logic because files may be locked by a previous MCP server process
341364 if (Test-Path $tempDir )
342365 {
343366 $maxRetries = 3
@@ -369,6 +392,12 @@ function Copy-McpServerToTemp
369392 }
370393 }
371394
395+ # Clean up stale PID file
396+ if (Test-Path $mcpPidFile )
397+ {
398+ Remove-Item $mcpPidFile - Force - ErrorAction SilentlyContinue
399+ }
400+
372401 New-Item - ItemType Directory - Path $tempDir - Force | Out-Null
373402 Write-Host " Created temporary directory: $tempDir " - ForegroundColor Cyan
374403
@@ -379,9 +408,18 @@ function Copy-McpServerToTemp
379408
380409 # Return the path to the executable and the temp directory for cleanup
381410 $tempExecutable = Join-Path $tempTargetDir $executableFile.Name
411+
412+ # Verify the executable was copied successfully
413+ if (-not (Test-Path $tempExecutable ))
414+ {
415+ throw " Failed to copy MCP server executable to temporary directory. Expected at: $tempExecutable "
416+ }
417+ Write-Host " Verified MCP server executable at: $tempExecutable " - ForegroundColor Cyan
418+
382419 return @ {
383420 ExecutablePath = $tempExecutable
384421 TempDirectory = $tempDir
422+ DirectoryHash = $directoryHash
385423 IsExe = $executableFile.Extension -eq " .exe"
386424 }
387425}
@@ -471,6 +509,34 @@ if ($Claude -and -not $NoMcp)
471509{
472510 try
473511 {
512+ # Kill existing MCP server BEFORE building (it may have files locked)
513+ # Compute the hash to find the PID file
514+ $hashBytes = (New-Object - TypeName System.Security.Cryptography.SHA256Managed).ComputeHash([System.Text.Encoding ]::UTF8.GetBytes($PSScriptRoot ))
515+ $mcpDirHash = [System.BitConverter ]::ToString($hashBytes , 0 , 4 ).Replace(" -" , " " ).ToLower()
516+ $mcpPidFilePath = Join-Path $env: TEMP " mcp-pid-$mcpDirHash .txt"
517+
518+ if (Test-Path $mcpPidFilePath )
519+ {
520+ try
521+ {
522+ $existingPid = (Get-Content $mcpPidFilePath - Raw).Trim()
523+ if ($existingPid -match ' ^\d+$' )
524+ {
525+ $process = Get-Process - Id $existingPid - ErrorAction SilentlyContinue
526+ if ($process )
527+ {
528+ Write-Host " Killing existing MCP server (PID: $existingPid ) before build..." - ForegroundColor Yellow
529+ Stop-Process - Id $existingPid - Force - ErrorAction SilentlyContinue
530+ Start-Sleep - Milliseconds 1000
531+ }
532+ }
533+ }
534+ catch
535+ {
536+ Write-Host " Could not kill existing MCP server: $_ " - ForegroundColor Yellow
537+ }
538+ }
539+
474540 Write-Host " Building MCP server before cleanup..." - ForegroundColor Cyan
475541 $mcpProjectPath = Join-Path $PSScriptRoot " $EngPath \src"
476542
@@ -1117,6 +1183,7 @@ if (-not $BuildImage)
11171183 # Start MCP approval server on host with dynamic port in new terminal tab
11181184 $mcpPort = $null
11191185 $mcpPortFile = $null
1186+ $mcpPidFile = $null
11201187 $mcpSecret = $null
11211188 $mcpTempDir = $null
11221189 if (-not $NoMcp )
@@ -1130,7 +1197,21 @@ if (-not $BuildImage)
11301197 }
11311198
11321199 Write-Host " Starting MCP approval server..." - ForegroundColor Green
1133- $mcpPortFile = Join-Path $env: TEMP " mcp-port-$ ( [System.Guid ]::NewGuid().ToString(' N' ).Substring(0 , 8 ) ) .txt"
1200+
1201+ # Use the MCP server snapshot saved before cleanup
1202+ $mcpServerInfo = $mcpServerSnapshot
1203+ $mcpTempDir = $mcpServerInfo.TempDirectory
1204+ $directoryHash = $mcpServerInfo.DirectoryHash
1205+
1206+ # Verify the executable still exists
1207+ if (-not (Test-Path $mcpServerInfo.ExecutablePath ))
1208+ {
1209+ throw " MCP server executable not found at: $ ( $mcpServerInfo.ExecutablePath ) . The temporary directory may have been cleaned up."
1210+ }
1211+
1212+ # Use deterministic file paths based on repo hash (allows multiple repos to have their own MCP servers)
1213+ $mcpPortFile = Join-Path $env: TEMP " mcp-port-$directoryHash .txt"
1214+ $mcpPidFile = Join-Path $env: TEMP " mcp-pid-$directoryHash .txt"
11341215
11351216 # Generate 128-bit (16 byte) random secret for authentication
11361217 $randomBytes = New-Object byte[] 16
@@ -1139,38 +1220,39 @@ if (-not $BuildImage)
11391220 $mcpSecret = [BitConverter ]::ToString($randomBytes ).Replace(' -' , ' ' ).ToLower()
11401221 Write-Host " Generated MCP authentication secret" - ForegroundColor Cyan
11411222
1142- # Use the MCP server snapshot saved before cleanup
1143- $mcpServerInfo = $mcpServerSnapshot
1144- $mcpTempDir = $mcpServerInfo.TempDirectory
1145-
11461223 # Build the command to run in the new tab
1224+ # Start the MCP server and capture its actual PID (not the PowerShell host PID)
11471225 if ($mcpServerInfo.IsExe )
11481226 {
1149- # Run executable directly
1150- $mcpCommand = " & '$ ( $mcpServerInfo.ExecutablePath ) ' tools mcp-server --port-file ' $mcpPortFile ' --secret ' $mcpSecret '"
1227+ # Run executable directly - use Start-Process to capture the actual process PID
1228+ $mcpCommand = " `$ proc = Start-Process -FilePath '$ ( $mcpServerInfo.ExecutablePath ) ' -ArgumentList 'tools',' mcp-server',' --port-file',' $mcpPortFile ',' --secret',' $mcpSecret ' -PassThru -NoNewWindow; `$ proc.Id | Set-Content -Path ' $mcpPidFile ' -NoNewline; Wait-Process -Id `$ proc.Id "
11511229 }
11521230 else
11531231 {
1154- # Run DLL with dotnet
1155- $mcpCommand = " dotnet '$ ( $mcpServerInfo.ExecutablePath ) ' tools mcp-server --port-file ' $mcpPortFile ' --secret ' $mcpSecret '"
1232+ # Run DLL with dotnet - use Start-Process to capture the actual process PID
1233+ $mcpCommand = " `$ proc = Start-Process -FilePath ' dotnet' -ArgumentList '$ ( $mcpServerInfo.ExecutablePath ) ',' tools',' mcp-server',' --port-file',' $mcpPortFile ',' --secret',' $mcpSecret ' -PassThru -NoNewWindow; `$ proc.Id | Set-Content -Path ' $mcpPidFile ' -NoNewline; Wait-Process -Id `$ proc.Id "
11561234 }
11571235
1236+ # Encode command as base64 to avoid quoting issues when passing through wt.exe
1237+ $mcpCommandBytes = [System.Text.Encoding ]::Unicode.GetBytes($mcpCommand )
1238+ $mcpCommandBase64 = [Convert ]::ToBase64String($mcpCommandBytes )
1239+
11581240 # Try Windows Terminal first (wt.exe), fall back to conhost
11591241 $wtPath = Get-Command wt.exe - ErrorAction SilentlyContinue
11601242 if ($wtPath )
11611243 {
11621244 # Open new tab in current Windows Terminal window
11631245 # The -w 0 option targets the current window
1164- # Use single argument string for proper escaping
1246+ # Use -EncodedCommand to avoid quoting issues with complex commands
11651247 # NOTE: --startingDirectory must be specified because Start-Process's -WorkingDirectory doesn't pass through to wt.exe
1166- $wtArgString = " -w 0 new-tab --title `" MCP Approval Server - $PSScriptRoot `" --startingDirectory `" $PSScriptRoot `" -- pwsh -NoExit -Command `" $mcpCommand `" "
1248+ $wtArgString = " -w 0 new-tab --title `" MCP Approval Server - $PSScriptRoot `" --startingDirectory `" $PSScriptRoot `" -- pwsh -NoExit -EncodedCommand $mcpCommandBase64 "
11671249 $mcpServerProcess = Start-Process - FilePath " wt.exe" - ArgumentList $wtArgString - PassThru
11681250 }
11691251 else
11701252 {
1171- # Fallback: start in new console window
1253+ # Fallback: start in new console window (use EncodedCommand here too for consistency)
11721254 $mcpServerProcess = Start-Process - FilePath " pwsh" `
1173- - ArgumentList " -NoExit" , " -Command " , $mcpCommand `
1255+ - ArgumentList " -NoExit" , " -EncodedCommand " , $mcpCommandBase64 `
11741256 - WorkingDirectory $PSScriptRoot `
11751257 - PassThru
11761258 }
@@ -1202,6 +1284,10 @@ if (-not $BuildImage)
12021284 {
12031285 Stop-Process - Id $mcpServerProcess.Id - Force - ErrorAction SilentlyContinue
12041286 }
1287+ if ($mcpPidFile -and (Test-Path $mcpPidFile ))
1288+ {
1289+ Remove-Item $mcpPidFile - Force - ErrorAction SilentlyContinue
1290+ }
12051291 if ($mcpTempDir -and (Test-Path $mcpTempDir ))
12061292 {
12071293 Remove-Item $mcpTempDir - Recurse - Force - ErrorAction SilentlyContinue
@@ -1385,41 +1471,23 @@ if (-not $BuildImage)
13851471 {
13861472 Write-Host " Stopping MCP approval server..." - ForegroundColor Cyan
13871473
1388- # Find the process listening on the MCP port and kill it
1389- try
1474+ # Kill MCP server using PID file
1475+ if ( $mcpPidFile -and ( Test-Path $mcpPidFile ))
13901476 {
1391- # Find PID using netstat
1392- $netstatOutput = netstat - ano | Select-String " :$mcpPort \s" | Select-Object - First 1
1393- if ($netstatOutput )
1477+ try
13941478 {
1395- $parts = $netstatOutput.Line.Trim () -split ' \s+'
1396- $mcpPid = $parts [-1 ]
1397- if ($mcpPid -and $mcpPid -match ' ^\d+$' )
1479+ $mcpPid = (Get-Content $mcpPidFile - Raw).Trim()
1480+ if ($mcpPid -match ' ^\d+$' )
13981481 {
13991482 Stop-Process - Id $mcpPid - Force - ErrorAction SilentlyContinue
14001483 Write-Host " Stopped MCP server process (PID: $mcpPid )" - ForegroundColor Cyan
1484+ # Wait for process to fully release file locks
1485+ Start-Sleep - Milliseconds 1000
14011486 }
14021487 }
1403- }
1404- catch
1405- {
1406- Write-Host " Could not stop MCP server via port lookup: $_ " - ForegroundColor Yellow
1407- }
1408-
1409- # Fallback: try to find by command line
1410- $mcpProcesses = Get-Process - Name pwsh, dotnet - ErrorAction SilentlyContinue |
1411- Where-Object { $_.CommandLine -like " *mcp-server*" }
1412-
1413- foreach ($proc in $mcpProcesses )
1414- {
1415- try
1416- {
1417- Stop-Process - Id $proc.Id - Force - ErrorAction SilentlyContinue
1418- Write-Host " Stopped MCP server process $ ( $proc.Id ) " - ForegroundColor Cyan
1419- }
14201488 catch
14211489 {
1422- # Process may have already exited
1490+ Write-Host " Could not stop MCP server: $_ " - ForegroundColor Yellow
14231491 }
14241492 }
14251493 }
@@ -1431,10 +1499,30 @@ if (-not $BuildImage)
14311499 }
14321500
14331501 # Clean up temporary MCP server directory
1502+ # Only delete PID file if temp directory cleanup succeeds (so next run can still kill the process if needed)
1503+ $tempDirCleaned = $false
14341504 if ($mcpTempDir -and (Test-Path $mcpTempDir ))
14351505 {
14361506 Write-Host " Cleaning up temporary MCP server directory: $mcpTempDir " - ForegroundColor Cyan
1437- Remove-Item $mcpTempDir - Recurse - Force - ErrorAction SilentlyContinue
1507+ try
1508+ {
1509+ Remove-Item $mcpTempDir - Recurse - Force - ErrorAction Stop
1510+ $tempDirCleaned = $true
1511+ }
1512+ catch
1513+ {
1514+ Write-Host " Could not clean up temp directory (will retry on next run): $_ " - ForegroundColor Yellow
1515+ }
1516+ }
1517+ else
1518+ {
1519+ $tempDirCleaned = $true
1520+ }
1521+
1522+ # Only clean up PID file if temp directory was successfully cleaned
1523+ if ($tempDirCleaned -and $mcpPidFile -and (Test-Path $mcpPidFile ))
1524+ {
1525+ Remove-Item $mcpPidFile - ErrorAction SilentlyContinue
14381526 }
14391527 }
14401528 }
0 commit comments