Skip to content

Commit 5f78323

Browse files
authored
Merge pull request #22593 from unoplatform/mergify/bp/release/stable/6.5/pr-22587
fix(mcp): Ensure devserver is started on calling uno_app_set_roots (backport #22587)
2 parents 3abc27b + 1127fa6 commit 5f78323

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed

build/test-scripts/run-devserver-cli-tests.ps1

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,181 @@ function Test-McpModeWithoutSolutionDir {
710710
}
711711
}
712712

713+
function Test-McpModeWithRootsFallback {
714+
param(
715+
[string]$SlnDir,
716+
[int]$DefaultPort,
717+
[int]$MaxAllocatedPort,
718+
[int]$CheckAttempts = 5,
719+
[int]$MaxAttempts = 30
720+
)
721+
722+
Write-Log "Validating MCP mode with --force-roots-fallback: devserver should start only after SetRoots is called"
723+
724+
$mcpTestPort = if ($MaxAllocatedPort -ge 65533) { $DefaultPort + 3 } else { $MaxAllocatedPort + 1 }
725+
726+
$process = $null
727+
$mcpProcessStarted = $false
728+
$stdoutLogPath = [System.IO.Path]::GetTempFileName()
729+
$stderrLogPath = [System.IO.Path]::GetTempFileName()
730+
731+
try {
732+
# Start MCP with --force-roots-fallback flag - devserver should NOT start until roots are provided
733+
$mcpArguments = Get-DevserverCliArguments -Arguments @("--mcp-app", "--force-roots-fallback", "--port", $mcpTestPort.ToString([System.Globalization.CultureInfo]::InvariantCulture), "-l", "trace")
734+
735+
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
736+
$startInfo.FileName = $script:DevServerHostExecutable
737+
foreach ($arg in $mcpArguments) {
738+
[void]$startInfo.ArgumentList.Add($arg)
739+
}
740+
$startInfo.WorkingDirectory = $SlnDir
741+
$startInfo.UseShellExecute = $false
742+
$startInfo.CreateNoWindow = $true
743+
$startInfo.RedirectStandardOutput = $true
744+
$startInfo.RedirectStandardError = $true
745+
$startInfo.RedirectStandardInput = $true
746+
$startInfo.StandardOutputEncoding = [System.Text.Encoding]::UTF8
747+
$startInfo.StandardErrorEncoding = [System.Text.Encoding]::UTF8
748+
749+
$process = New-Object System.Diagnostics.Process
750+
$process.StartInfo = $startInfo
751+
752+
if (-not $process.Start()) {
753+
throw "Failed to start MCP proxy process for roots-fallback test."
754+
}
755+
$mcpProcessStarted = $true
756+
757+
# Start background tasks to capture stdout/stderr to files
758+
$stdoutStream = New-Object System.IO.FileStream($stdoutLogPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)
759+
$stderrStream = New-Object System.IO.FileStream($stderrLogPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)
760+
$cts = New-Object System.Threading.CancellationTokenSource
761+
$stdoutTask = $process.StandardOutput.BaseStream.CopyToAsync($stdoutStream, 81920, $cts.Token)
762+
$stderrTask = $process.StandardError.BaseStream.CopyToAsync($stderrStream, 81920, $cts.Token)
763+
764+
# Give the MCP proxy a moment to initialize
765+
Start-Sleep -Seconds 5
766+
767+
# PHASE 1: Verify the devserver does NOT appear in the list (because roots-fallback is enabled and no roots were set)
768+
Write-Log "Phase 1: Verifying devserver did NOT auto-start..."
769+
$foundInList = $false
770+
for ($attempt = 1; $attempt -le $CheckAttempts; $attempt++) {
771+
Write-Log "Checking devserver list for absence of auto-started instance (attempt $attempt/$CheckAttempts)..."
772+
773+
$listOutput = ""
774+
try {
775+
$listOutput = Invoke-DevserverCli -Arguments @('list') 2>&1
776+
}
777+
catch {
778+
$listOutput = $_.Exception.Message
779+
}
780+
781+
$listText = ($listOutput | Out-String)
782+
$portPattern = "Port\s*:\s*$mcpTestPort"
783+
$endpointPattern = ":$mcpTestPort(\b|/)"
784+
785+
if ($LASTEXITCODE -eq 0 -and ($listText -match $portPattern -or $listText -match $endpointPattern)) {
786+
$foundInList = $true
787+
break
788+
}
789+
790+
Start-Sleep -Seconds 2
791+
}
792+
793+
if ($foundInList) {
794+
$stdoutLog = if (Test-Path $stdoutLogPath) { Get-Content $stdoutLogPath -Raw -ErrorAction SilentlyContinue } else { "" }
795+
$stderrLog = if (Test-Path $stderrLogPath) { Get-Content $stderrLogPath -Raw -ErrorAction SilentlyContinue } else { "" }
796+
throw "Devserver with --force-roots-fallback should NOT have started automatically, but it appeared in 'uno-devserver list'.`nSTDOUT:`n$stdoutLog`nSTDERR:`n$stderrLog"
797+
}
798+
799+
Write-Log "Phase 1 passed: Devserver did NOT auto-start (as expected)"
800+
801+
# PHASE 2: Send MCP initialize and SetRoots call via STDIO, then verify devserver starts
802+
Write-Log "Phase 2: Sending MCP initialize and SetRoots via STDIO..."
803+
804+
# MCP JSON-RPC initialize request
805+
$initializeRequest = @{
806+
jsonrpc = "2.0"
807+
id = 1
808+
method = "initialize"
809+
params = @{
810+
protocolVersion = "2024-11-05"
811+
capabilities = @{}
812+
clientInfo = @{
813+
name = "test-client"
814+
version = "1.0.0"
815+
}
816+
}
817+
} | ConvertTo-Json -Depth 10 -Compress
818+
819+
# MCP JSON-RPC tools/call request for uno_app_set_roots
820+
$normalizedSlnDir = $SlnDir -replace '\\', '/'
821+
$setRootsRequest = @{
822+
jsonrpc = "2.0"
823+
id = 2
824+
method = "tools/call"
825+
params = @{
826+
name = "uno_app_set_roots"
827+
arguments = @{
828+
roots = @($normalizedSlnDir)
829+
}
830+
}
831+
} | ConvertTo-Json -Depth 10 -Compress
832+
833+
# Write requests to stdin (each message is a single line in MCP STDIO transport)
834+
$stdinWriter = $process.StandardInput
835+
$stdinWriter.WriteLine($initializeRequest)
836+
$stdinWriter.Flush()
837+
838+
Start-Sleep -Seconds 2
839+
840+
$stdinWriter.WriteLine($setRootsRequest)
841+
$stdinWriter.Flush()
842+
843+
Write-Log "Sent SetRoots request with root: $normalizedSlnDir"
844+
845+
# PHASE 3: Verify the devserver NOW appears in the list
846+
Write-Log "Phase 3: Verifying devserver started after SetRoots..."
847+
$mcpStarted = Wait-ForDevserverListEntry -Port $mcpTestPort -SolutionDirectory $SlnDir -MaxAttempts $MaxAttempts -DelaySeconds 2
848+
849+
if (-not $mcpStarted) {
850+
$stdoutLog = if (Test-Path $stdoutLogPath) { Get-Content $stdoutLogPath -Raw -ErrorAction SilentlyContinue } else { "" }
851+
$stderrLog = if (Test-Path $stderrLogPath) { Get-Content $stderrLogPath -Raw -ErrorAction SilentlyContinue } else { "" }
852+
throw "Devserver with --force-roots-fallback did NOT start after SetRoots was called via MCP STDIO.`nSTDOUT:`n$stdoutLog`nSTDERR:`n$stderrLog"
853+
}
854+
855+
Write-Log "Phase 3 passed: Devserver started after SetRoots call"
856+
Write-Log "Test-McpModeWithRootsFallback completed successfully"
857+
}
858+
finally {
859+
# Clean up
860+
if ($cts) {
861+
try { $cts.Cancel() } catch {}
862+
try { $cts.Dispose() } catch {}
863+
}
864+
865+
if ($process -and $mcpProcessStarted -and -not $process.HasExited) {
866+
try { $process.Kill() } catch {}
867+
try { $process.WaitForExit(5000) } catch {}
868+
}
869+
870+
if ($stdoutStream) {
871+
try { $stdoutStream.Flush() } catch {}
872+
try { $stdoutStream.Dispose() } catch {}
873+
}
874+
if ($stderrStream) {
875+
try { $stderrStream.Flush() } catch {}
876+
try { $stderrStream.Dispose() } catch {}
877+
}
878+
879+
Stop-DevserverInDirectory -Directory $SlnDir
880+
881+
if (Test-Path $stdoutLogPath) { Remove-Item $stdoutLogPath -ErrorAction SilentlyContinue }
882+
if (Test-Path $stderrLogPath) { Remove-Item $stderrLogPath -ErrorAction SilentlyContinue }
883+
}
884+
885+
return $mcpTestPort
886+
}
887+
713888
function Stop-DevserverInDirectory {
714889
param([string]$Directory)
715890

@@ -796,6 +971,8 @@ try {
796971
$primaryPort = Test-DevserverStartStop -SlnDir $slnDir -CsprojDir $csprojDir -CsprojPath $csprojPath -DefaultPort $defaultPort -MaxAttempts $maxAttempts
797972
$solutionDirTestPort = Test-DevserverSolutionDirSupport -SlnDir $slnDir -DefaultPort $defaultPort -BaselinePort $primaryPort -MaxAttempts $maxAttempts
798973
Test-McpModeWithoutSolutionDir -SlnDir $slnDir -DefaultPort $defaultPort -PrimaryPort $primaryPort -SolutionDirPort $solutionDirTestPort -MaxAttempts $maxAttempts
974+
$maxAllocatedPort = [Math]::Max($primaryPort, $solutionDirTestPort)
975+
Test-McpModeWithRootsFallback -SlnDir $slnDir -DefaultPort $defaultPort -MaxAllocatedPort $maxAllocatedPort
799976
Test-CodexIntegration -SlnDir $slnDir
800977

801978
$finalExitCode = 0

src/Uno.UI.DevServer.Cli/Mcp/McpProxy.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ private static string NormalizeLocalFilePath(string localPath)
188188

189189
private void EnsureDevServerStartedFromSolutionDirectory()
190190
{
191+
if (_forceRootsFallback)
192+
{
193+
_logger.LogTrace("Roots fallback is enabled; skipping initial DevServer start (waiting for roots)");
194+
return;
195+
}
196+
191197
var directory = string.IsNullOrWhiteSpace(_solutionDirectory)
192198
? _currentDirectory
193199
: _solutionDirectory;

0 commit comments

Comments
 (0)