# Synapse MCP installer for Windows. # # powershell -c "irm https://downloads.synapse-mcp.dev/install.ps1 | iex" # # Fetches the precompiled launcher for windows-x86_64 from the versioned # download prefix (default latest/) and installs it as synapse-mcp.exe on the # user's PATH. No admin rights required - everything lands under %LOCALAPPDATA% # and PATH is edited at User scope. # # WHY ONLY '#' LINE COMMENTS, AND NO #Requires: # This file is executed by being piped into Invoke-Expression, never as a saved # .ps1, so it has to survive that path exactly. Two things bit us there: # - A leading `<# ... #>` block comment is the single most fragile way to open # such a script. If the fetched text ever reaches iex as more than one item # (Windows PowerShell 5.1's Invoke-RestMethod does not always hand back one # string the way pwsh 7 does), each item is parsed on its own, the piece # starting `<#` has no terminator, and the whole thing dies with # "The terminator '#>' is missing from the multiline comment" before a # single line runs. Line comments cannot fail that way. # - #Requires is only honoured for a script file on disk; through iex it is # inert, so it bought nothing and only added a second unusual leading token. # DO NOT "harden" the published one-liner by piping through Out-String. That # was tried, on Windows PowerShell 5.1, to guard against a suspected # array-of-lines delivery. It made things strictly worse: Out-String handed the # parser this file with its double quotes consumed ("" collapsed to ", "..." # lost both), so every string literal below broke at once. Plain irm | iex is # correct and is what ships. Neither failure reproduces on pwsh 7, so a change # to the one-liner is only ever verified by running it on real 5.1. # # WHY THE VERSIONED PREFIX and not a flat bucket-root key: the root key only # ever held a single macOS binary, so there is no Windows equivalent to fetch. # # SECURITY NOTE: integrity here relies on HTTPS transport security only - there # is no signature verification of the downloaded binary in this script, matching # dist/install.sh. The launcher already trusts a pinned Ed25519 key to verify # signed manifests for the Elixir runtime it decrypts (see release_manifest.rs); # extending that to cover the launcher binary itself is best done by having the # freshly-installed binary verify its own signature on first run, not by # reimplementing Ed25519 verification in PowerShell here. Note the launcher .exe # is also not Authenticode-signed, which is what makes Microsoft Defender's ML # heuristics flag it (Trojan:Win32/Commando.A!ml) on first download. # # Overrides (environment variables, to stay usable through iex where no # parameters can be passed): # $env:SYNAPSE_MCP_VERSION = 'v3.12.0' # default: latest # $env:SYNAPSE_MCP_BASE_URL = 'https://downloads.synapse-mcp.dev/synapse-mcp' # $env:SYNAPSE_MCP_INSTALL_DIR = "$env:LOCALAPPDATA\Programs\synapse-mcp" # $env:SYNAPSE_MCP_PLATFORM = 'windows-x86_64.exe' # $env:SYNAPSE_MCP_NO_PATH = '1' # skip the PATH edit $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest function Write-Step($Message) { Write-Host $Message -ForegroundColor Cyan } function Write-Note($Message) { Write-Host $Message -ForegroundColor DarkGray } function Get-EnvOrDefault($Name, $Default) { $value = [Environment]::GetEnvironmentVariable($Name, 'Process') if ([string]::IsNullOrWhiteSpace($value)) { return $Default } return $value } function Install-SynapseMcp { # PowerShell 5.1 on older Windows still defaults to SSL3/TLS1.0, which R2 # refuses; force TLS 1.2 without clobbering anything already enabled. try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch { Write-Note "Could not raise TLS version; continuing with system default." } $version = Get-EnvOrDefault 'SYNAPSE_MCP_VERSION' 'latest' $baseUrl = (Get-EnvOrDefault 'SYNAPSE_MCP_BASE_URL' 'https://downloads.synapse-mcp.dev/synapse-mcp').TrimEnd('/') # LOCALAPPDATA is always set on Windows, but the fallback keeps this from # throwing on a non-Windows host (CI syntax/behaviour checks run there) -- # PowerShell evaluates the default argument eagerly, before the override is # even consulted, so a null here would break the SYNAPSE_MCP_INSTALL_DIR # path too. $localAppData = $env:LOCALAPPDATA if ([string]::IsNullOrWhiteSpace($localAppData)) { $localAppData = Join-Path ([Environment]::GetFolderPath('UserProfile')) 'AppData\Local' } $installDir = Get-EnvOrDefault 'SYNAPSE_MCP_INSTALL_DIR' (Join-Path $localAppData 'Programs\synapse-mcp') $binName = 'synapse-mcp.exe' $arch = $env:PROCESSOR_ARCHITECTURE if ([string]::IsNullOrWhiteSpace($arch)) { $arch = 'AMD64' } switch ($arch.ToUpperInvariant()) { 'AMD64' { $platform = 'windows-x86_64.exe' } 'X86' { # A 32-bit shell on 64-bit Windows reports X86; PROCESSOR_ARCHITEW6432 # is the honest answer in that case. if ($env:PROCESSOR_ARCHITEW6432) { $platform = 'windows-x86_64.exe' } else { throw "32-bit Windows is not supported - Synapse MCP ships x86_64 only." } } 'ARM64' { # Windows 11 on ARM runs x64 binaries under emulation, so this works, # just slower than a native build would. Write-Note "ARM64 detected - installing the x86_64 build (runs under Windows x64 emulation)." $platform = 'windows-x86_64.exe' } default { throw "Unsupported processor architecture: $arch" } } $platform = Get-EnvOrDefault 'SYNAPSE_MCP_PLATFORM' $platform $url = "$baseUrl/$version/synapse-mcp-$platform" $tmp = Join-Path ([IO.Path]::GetTempPath()) ("synapse-mcp-" + [Guid]::NewGuid().ToString('N') + ".exe") try { New-Item -ItemType Directory -Force -Path $installDir | Out-Null Write-Step "Downloading $url" # Invoke-WebRequest's progress bar makes 5.1 downloads roughly an order # of magnitude slower; suppress it just for the transfer. $previousProgress = $ProgressPreference $ProgressPreference = 'SilentlyContinue' try { Invoke-WebRequest -Uri $url -OutFile $tmp -UseBasicParsing -TimeoutSec 300 } finally { $ProgressPreference = $previousProgress } # Guard against an error page or truncated transfer being installed and # then failing later as a baffling "not a valid application". $size = (Get-Item $tmp).Length if ($size -lt 1MB) { throw "Downloaded file is only $size bytes - expected a multi-megabyte executable. The download URL may be wrong or the release incomplete." } $header = [byte[]]::new(2) $stream = [IO.File]::OpenRead($tmp) try { $null = $stream.Read($header, 0, 2) } finally { $stream.Dispose() } if ($header[0] -ne 0x4D -or $header[1] -ne 0x5A) { throw "Downloaded file is not a Windows executable (missing MZ header): $url" } $installPath = Join-Path $installDir $binName try { Move-Item -Path $tmp -Destination $installPath -Force } catch [IO.IOException] { throw "Could not write $installPath - a running synapse-mcp.exe is holding the file. Stop it (Get-Process synapse-mcp | Stop-Process) and re-run this installer." } Add-ToUserPath $installDir Write-Host "" Write-Host "Installed synapse-mcp to $installPath" -ForegroundColor Green Write-Host "" Write-Host "Next steps:" Write-Host " 1. Open a new terminal (so the updated PATH is picked up)." Write-Host " 2. Run: synapse-mcp install" } finally { if (Test-Path $tmp) { Remove-Item $tmp -Force -ErrorAction SilentlyContinue } } } function Add-ToUserPath($Directory) { if ((Get-EnvOrDefault 'SYNAPSE_MCP_NO_PATH' '') -ne '') { Write-Note "SYNAPSE_MCP_NO_PATH set - leaving PATH untouched." return } # Read the User scope specifically: $env:PATH is User+Machine merged, and # writing that back would copy every Machine entry into the user's PATH. $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if ($null -eq $userPath) { $userPath = '' } $entries = $userPath.Split(';') | Where-Object { $_ -ne '' } $already = $entries | Where-Object { $_.TrimEnd('\') -ieq $Directory.TrimEnd('\') } if ($already) { Write-Note "$Directory is already on your PATH." } else { $updated = if ($userPath -eq '') { $Directory } else { $userPath.TrimEnd(';') + ';' + $Directory } try { [Environment]::SetEnvironmentVariable('Path', $updated, 'User') Write-Note "Added $Directory to your user PATH." } catch { Write-Warning "Could not update PATH automatically. Add this directory manually: $Directory" return } } # Make it usable in the shell that ran the installer, too. if (($env:PATH -split ';') -notcontains $Directory) { $env:PATH = "$env:PATH;$Directory" } } try { Install-SynapseMcp } catch { Write-Host "" Write-Host "Install failed: $($_.Exception.Message)" -ForegroundColor Red Write-Host "See https://synapse-mcp.dev/download for manual install instructions." -ForegroundColor DarkGray exit 1 }