diff --git a/eng/common/mcp/azure-sdk-mcp.ps1 b/eng/common/mcp/azure-sdk-mcp.ps1 index 331155516..b90d85b8c 100755 --- a/eng/common/mcp/azure-sdk-mcp.ps1 +++ b/eng/common/mcp/azure-sdk-mcp.ps1 @@ -20,6 +20,54 @@ $ErrorActionPreference = "Stop" $toolInstallDirectory = $InstallDirectory ? $InstallDirectory : (Get-CommonInstallDirectory) +$mcpMode = $Run + +# Log to console or MCP client json-rpc +function log([object]$message, [switch]$warn, [switch]$err) { + [string]$messageString = $message + + # Assume we are in an MCP client context when `-Run` is specified + # otherwise print to console normally + if (!$mcpMode) { + if ($err) { + Write-Error $messageString -ErrorAction Continue + } + elseif ($warn) { + Write-Warning $messageString + } + else { + Write-Host $messageString + } + return; + } + + $level = switch ($message) { + { $_ -is [System.Management.Automation.ErrorRecord] } { 'error' } + { $_ -is [System.Management.Automation.WarningRecord] } { 'warning' } + default { 'notice' } + } + + # If message stringifies as a valid error message we want to print it + # otherwise print stack for calls to external binaries + if ($messageString -eq 'System.Management.Automation.RemoteException') { + $messageString = $message.ScriptStackTrace + } + + # Log json-rpc messages so the MCP client doesn't print + # '[warning] Failed to parse message:' + $messageObject = @{ + jsonrpc = "2.0" + method = "notifications/message" + params = @{ + level = $level + logger = "installer" + data = $messageString + } + } + + Write-Host ($messageObject | ConvertTo-Json -Depth 100 -Compress) +} + if ($UpdateVsCodeConfig) { $vscodeConfigPath = Join-Path $PSScriptRoot ".." ".." ".." ".vscode" "mcp.json" if (Test-Path $vscodeConfigPath) { @@ -45,7 +93,7 @@ if ($UpdateVsCodeConfig) { } } $vscodeConfig.servers = $orderedServers - Write-Host "Updating vscode mcp config at $vscodeConfigPath" + log "Updating vscode mcp config at $vscodeConfigPath" $vscodeConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $vscodeConfigPath -Force } @@ -54,12 +102,36 @@ if ($UpdateVsCodeConfig) { $tmp = $env:TEMP ? $env:TEMP : [System.IO.Path]::GetTempPath() $guid = [System.Guid]::NewGuid() $tempInstallDirectory = Join-Path $tmp "azsdk-install-$($guid)" -$tempExe = Install-Standalone-Tool ` - -Version $Version ` - -FileName $FileName ` - -Package $Package ` - -Directory $tempInstallDirectory ` - -Repository $Repository + +if ($mcpMode) { + try { + # Swallow all output and re-log so we can wrap any + # output from the inner function as json-rpc + $tempExe = Install-Standalone-Tool ` + -Version $Version ` + -FileName $FileName ` + -Package $Package ` + -Directory $tempInstallDirectory ` + -Repository $Repository ` + *>&1 + | Tee-Object -Variable _ + | ForEach-Object { log $_; $_ } + | Select-Object -Last 1 + } + catch { + log -err $_ + exit 1 + } +} +else { + $tempExe = Install-Standalone-Tool ` + -Version $Version ` + -FileName $FileName ` + -Package $Package ` + -Directory $tempInstallDirectory ` + -Repository $Repository ` + +} if (-not (Test-Path $toolInstallDirectory)) { New-Item -ItemType Directory -Path $toolInstallDirectory -Force | Out-Null @@ -68,12 +140,13 @@ $exeName = Split-Path $tempExe -Leaf $exeDestination = Join-Path $toolInstallDirectory $exeName Copy-Item -Path $tempExe -Destination $exeDestination -Force -Write-Host "Package $package is installed at $exeDestination" +log "Package $package is installed at $exeDestination" if (!$UpdatePathInProfile) { - Write-Warning "To add the tool to PATH for new shell sessions, re-run with -UpdatePathInProfile to modify the shell profile file." -} else { + log -warn "To add the tool to PATH for new shell sessions, re-run with -UpdatePathInProfile to modify the shell profile file." +} +else { Add-InstallDirectoryToPathInProfile -InstallDirectory $toolInstallDirectory - Write-Warning "'$exeName' will be available in PATH for new shell sessions." + log -warn "'$exeName' will be available in PATH for new shell sessions." } if ($Run) {