-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.ps1
More file actions
136 lines (120 loc) · 5.53 KB
/
Copy pathinstall.ps1
File metadata and controls
136 lines (120 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# UTMStack MCP server installer (Windows)
#
# irm https://raw.githubusercontent.com/utmstack/MCP/main/install.ps1 | iex
#
# Downloads the release build for this platform, verifies its SHA256 against
# the checksums published with the release, and installs it.
$ErrorActionPreference = 'Stop'
# On PowerShell 5.1 the Invoke-WebRequest progress bar makes large
# downloads roughly an order of magnitude slower.
$ProgressPreference = 'SilentlyContinue'
$Repo = 'utmstack/MCP'
$App = 'utmstack-mcp'
$Version = if ($env:UTMSTACK_MCP_VERSION) { $env:UTMSTACK_MCP_VERSION } else { 'latest' }
function Info($m) { Write-Host $m }
# `throw`, not `exit`: `irm ... | iex` runs in the caller's scope, so `exit`
# closes the user's console, and inside a &-invoked scriptblock (how the
# CLI installer calls this one) it escapes the caller's try/catch.
function Fail($m) { throw "utmstack-mcp install: $m" }
# --- platform ------------------------------------------------------------- #
# Under WOW64 a 32-bit PowerShell host reports x86 on an x64 machine.
$rawArch = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
$arch = switch ($rawArch) {
'AMD64' { 'amd64' }
'ARM64' { 'arm64' }
default { Fail "unsupported architecture: $rawArch" }
}
$asset = "$App-windows-$arch.zip"
# --- resolve release ------------------------------------------------------ #
$api = if ($Version -eq 'latest') {
"https://api.github.com/repos/$Repo/releases/latest"
} else {
"https://api.github.com/repos/$Repo/releases/tags/$Version"
}
Info "Resolving $Version release of $Repo ..."
try {
$release = Invoke-RestMethod -Uri $api -UseBasicParsing
} catch {
Fail "could not resolve a release (is the repository public and does it have a release?)"
}
$tag = $release.tag_name
if (-not $tag) { Fail 'no tag_name in release response' }
Info " version: $tag"
$base = "https://github.com/$Repo/releases/download/$tag"
$tmp = Join-Path $env:TEMP ("utmstack-mcp-install-" + [guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
try {
# --- download + verify ------------------------------------------------ #
Info "Downloading $asset ..."
$pkg = Join-Path $tmp $asset
try {
Invoke-WebRequest -Uri "$base/$asset" -OutFile $pkg -UseBasicParsing
} catch {
Fail "no build published for windows/$arch in $tag"
}
Info 'Verifying checksum ...'
$sumFile = Join-Path $tmp 'checksums.txt'
try {
Invoke-WebRequest -Uri "$base/checksums.txt" -OutFile $sumFile -UseBasicParsing
} catch {
Fail 'could not download checksums.txt - refusing to install unverified binary'
}
$expected = $null
foreach ($line in Get-Content $sumFile) {
if ($line -match "^([0-9a-fA-F]{64})\s+\*?$([regex]::Escape($asset))$") {
$expected = $Matches[1].ToLower(); break
}
}
if (-not $expected) { Fail "no checksum listed for $asset - refusing to install" }
$actual = (Get-FileHash -Path $pkg -Algorithm SHA256).Hash.ToLower()
if ($expected -ne $actual) { Fail "checksum mismatch - expected $expected, got $actual" }
Info ' checksum OK'
# --- install ---------------------------------------------------------- #
$libDir = Join-Path $env:LOCALAPPDATA "Programs\$App"
if (Test-Path $libDir) {
# Windows will not delete a running executable. On upgrade the previous
# utmstack-mcp is often still spawned by an MCP client, and an
# unguarded Remove-Item throws after partially deleting the directory.
try {
Remove-Item -Recurse -Force $libDir -ErrorAction Stop
} catch {
Fail "could not replace $libDir - utmstack-mcp may be running. Close your MCP client (Claude Code, OpenCode, ...) and re-run."
}
}
New-Item -ItemType Directory -Force -Path $libDir | Out-Null
Info "Installing to $libDir ..."
Expand-Archive -Path $pkg -DestinationPath $tmp -Force
$src = Get-ChildItem -Path $tmp -Directory | Where-Object { $_.Name -like "$App*" } | Select-Object -First 1
if (-not $src) { Fail 'unexpected archive layout' }
Copy-Item -Path (Join-Path $src.FullName '*') -Destination $libDir -Recurse -Force
$exe = Join-Path $libDir "$App.exe"
if (-not (Test-Path $exe)) { Fail "installed archive has no $App.exe" }
# --- PATH ------------------------------------------------------------- #
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($userPath -notlike "*$libDir*") {
Info "Adding $libDir to your user PATH ..."
$newPath = if ($userPath) { "$userPath;$libDir" } else { $libDir }
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
$env:Path = "$env:Path;$libDir"
$pathNote = $true
}
$installed = (& $exe --version 2>&1 | Out-String).Trim()
Info ''
Info "Installed $installed"
Info " binary: $exe"
if ($pathNote) {
Info ''
Info ' NOTE: PATH was updated. Open a new terminal for `utmstack-mcp` to resolve.'
}
Info ''
Info 'Next steps:'
Info " 1. $App init # configure your UTMStack connection"
Info ' 2. Register with a client:'
Info " Claude Code: claude mcp add --scope user utmstack -- $App"
Info ' OpenCode: add to %USERPROFILE%\.config\opencode\opencode.json:'
Info " {`"mcp`":{`"utmstack`":{`"type`":`"local`",`"command`":[`"$App`"],`"enabled`":true}}}"
Info ''
}
finally {
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
}