# codebase-memory-mcp setup script (Windows) # Default: install the pre-built native Windows binary through install.ps1 # -FromSource: build from source inside WSL (requires Go + gcc in WSL) param( [switch]$FromSource, [switch]$Help ) $ErrorActionPreference = "Stop" $Repo = "DeusData/codebase-memory-mcp" $BinaryName = "codebase-memory-mcp" $InstallDir = Join-Path $env:LOCALAPPDATA "codebase-memory-mcp" # --- Helpers --- function Write-Ok($msg) { Write-Host " $msg" -ForegroundColor Green } function Write-Fail($msg) { Write-Host " $msg" -ForegroundColor Red } function Write-Warn($msg) { Write-Host " $msg" -ForegroundColor Yellow } function Read-SettingsJson($Path) { # PS5.1-compatible: ConvertFrom-Json returns PSCustomObject, not Hashtable. # We convert to ordered hashtable manually. if (-not (Test-Path $Path)) { return @{} } $raw = Get-Content $Path -Raw if (-not $raw -or $raw.Trim() -eq "") { return @{} } $obj = $raw | ConvertFrom-Json $ht = [ordered]@{} foreach ($prop in $obj.PSObject.Properties) { if ($prop.Value -is [System.Management.Automation.PSCustomObject]) { $inner = [ordered]@{} foreach ($p in $prop.Value.PSObject.Properties) { $inner[$p.Name] = $p.Value } $ht[$prop.Name] = $inner } else { $ht[$prop.Name] = $prop.Value } } return $ht } function Write-SettingsJson($Path, $Settings) { # Back up existing file before writing if (Test-Path $Path) { Copy-Item $Path "$Path.bak" -Force } $Settings | ConvertTo-Json -Depth 10 | Set-Content $Path -Encoding UTF8 } function Configure-ClaudeCode($McpConfig) { Write-Host "" $answer = Read-Host "Configure Claude Code to use codebase-memory-mcp? [y/N]" if ($answer -match '^[Yy]$') { $settingsPath = Join-Path $env:USERPROFILE ".claude\settings.json" $settingsDir = Split-Path $settingsPath -Parent if (-not (Test-Path $settingsDir)) { New-Item -ItemType Directory -Path $settingsDir -Force | Out-Null } $settings = Read-SettingsJson $settingsPath if (-not $settings.Contains("mcpServers")) { $settings["mcpServers"] = [ordered]@{} } $settings["mcpServers"]["codebase-memory-mcp"] = $McpConfig Write-SettingsJson $settingsPath $settings Write-Ok "Updated $settingsPath" } else { Write-Host "" Write-Host " Add this to your .mcp.json or %USERPROFILE%\.claude\settings.json:" -ForegroundColor White Write-Host "" $snippet = @{ mcpServers = @{ "codebase-memory-mcp" = $McpConfig } } $snippet | ConvertTo-Json -Depth 10 | Write-Host } } function Test-WSL { try { $null = wsl.exe --status 2>&1 if ($LASTEXITCODE -ne 0) { return $false } return $true } catch { return $false } } function Get-WSLDistro { $output = wsl.exe -l -v 2>&1 | Out-String $lines = $output -split "`n" | Where-Object { $_ -match '\S' } | Select-Object -Skip 1 foreach ($line in $lines) { $clean = $line -replace '\x00', '' -replace '^\s+', '' if ($clean -match '^\*?\s*(\S+)\s+') { $name = $Matches[1] if ($name -ne "NAME" -and $name -ne "") { return $name } } } return $null } function Invoke-WSL { param([string]$Command) $result = wsl.exe -- bash -c $Command 2>&1 if ($LASTEXITCODE -ne 0) { throw "WSL command failed: $Command`n$result" } return $result } # --- Main --- if ($Help) { Write-Host "" Write-Host "Usage: .\setup-windows.ps1 [-FromSource] [-Help]" Write-Host "" Write-Host " Default: Download the pre-built Windows binary through install.ps1" Write-Host " -FromSource: Build from source inside WSL (requires Go 1.23+ and gcc in WSL)" Write-Host "" exit 0 } Write-Host "" Write-Host "codebase-memory-mcp installer (Windows)" -ForegroundColor White Write-Host "" if ($FromSource) { # --- Build from source via WSL --- Write-Host "Checking WSL2 (required for building from source)..." -ForegroundColor White if (-not (Test-WSL)) { Write-Fail "WSL2 is not available. Required for building from source (CGO needs gcc)." Write-Host "" Write-Host " Install WSL2:" -ForegroundColor Yellow Write-Host " wsl --install -d Ubuntu" Write-Host " Then restart your computer and run this script again." Write-Host "" Write-Host " Alternatively, download a pre-built binary (no WSL needed):" -ForegroundColor Yellow Write-Host " .\setup-windows.ps1" exit 1 } Write-Ok "WSL2 is available" $distro = Get-WSLDistro if (-not $distro) { Write-Fail "No WSL Linux distribution found." Write-Host "" Write-Host " Install Ubuntu:" -ForegroundColor Yellow Write-Host " wsl --install -d Ubuntu" exit 1 } Write-Ok "WSL distro: $distro" $wslUser = (Invoke-WSL "whoami").Trim() Write-Ok "WSL user: $wslUser" Write-Host "" Write-Host "Checking prerequisites inside WSL..." -ForegroundColor White # Check for gcc try { Invoke-WSL "command -v gcc" | Out-Null Write-Ok "gcc found" } catch { Write-Warn "gcc not found. Installing build-essential..." Invoke-WSL "sudo apt-get update && sudo apt-get install -y build-essential" } # Check for Go try { $goVersion = Invoke-WSL "go version" Write-Ok "Go: $goVersion" } catch { Write-Fail "Go not found in WSL." Write-Host "" Write-Host " Install Go 1.23+ inside WSL:" -ForegroundColor Yellow Write-Host " See https://go.dev/dl/ for Linux amd64 tarball" exit 1 } # Check for git try { Invoke-WSL "command -v git" | Out-Null Write-Ok "git found" } catch { Write-Fail "git not found in WSL. Install with: sudo apt install git" exit 1 } # Clone or update Write-Host "" $sourceDir = "/home/$wslUser/.local/share/codebase-memory-mcp" try { Invoke-WSL "test -d $sourceDir/.git" | Out-Null Write-Host "Updating source..." -ForegroundColor White Invoke-WSL "git -C $sourceDir pull --ff-only" } catch { Write-Host "Cloning repository..." -ForegroundColor White Invoke-WSL "mkdir -p /home/$wslUser/.local/share && git clone https://github.com/$Repo.git $sourceDir" } Write-Ok "Source at $sourceDir" # Build Write-Host "" Write-Host "Building binary (this may take a minute)..." -ForegroundColor White $wslBinaryPath = "/home/$wslUser/.local/bin/$BinaryName" Invoke-WSL "mkdir -p /home/$wslUser/.local/bin && cd $sourceDir && scripts/build.sh && cp build/c/codebase-memory-mcp $wslBinaryPath" Write-Ok "Built to $wslBinaryPath (inside WSL)" # Verify try { Invoke-WSL "test -x $wslBinaryPath" | Out-Null Write-Ok "Binary is executable" } catch { Write-Fail "Binary at $wslBinaryPath is not executable" exit 1 } # Configure -- WSL binary needs wsl.exe wrapper $mcpConfig = [ordered]@{ type = "stdio" command = "wsl.exe" args = @("-d", $distro, "--", $wslBinaryPath) } Configure-ClaudeCode $mcpConfig Write-Host "" Write-Ok "Done! Restart Claude Code and verify with /mcp" Write-Host "" Write-Host " To uninstall:" -ForegroundColor White Write-Host " wsl.exe -- rm $wslBinaryPath" Write-Host " wsl.exe -- rm -rf $sourceDir" Write-Host " wsl.exe -- rm -rf ~/.cache/codebase-memory-mcp/" } else { # --- Download + install through install.ps1 --- # # install.ps1 is the one implementation of "fetch a release and install # it": it downloads checksums.txt next to the archive, verifies the # archive's SHA-256 against it, validates the zip layout and only then # runs the binary's own `install`. This script used to carry a second # copy of that download, which did not keep up with the installer. It # now fetches install.ps1 from the same origin and branch it is itself # served from and hands over to it, so there is exactly one install path. # # CBM_DOWNLOAD_URL (the installers' download-base override, for local # testing) also moves the installer fetch: install.ps1 is then taken from # "$env:CBM_DOWNLOAD_URL/install.ps1". $installerUrl = "https://raw.githubusercontent.com/$Repo/main/install.ps1" if ($env:CBM_DOWNLOAD_URL) { $installerUrl = $env:CBM_DOWNLOAD_URL.TrimEnd('/') + "/install.ps1" } # Same transport rule as install.ps1: HTTPS, or plain HTTP for a loopback # authority only (the local test fixture). No redirects are followed: both # origins serve the installer directly. try { $installerUri = [Uri]$installerUrl } catch { $installerUri = $null } $loopbackHttp = ( $installerUri -and $installerUri.IsAbsoluteUri -and $installerUri.Scheme -eq "http" -and $installerUri.IsLoopback -and [string]::IsNullOrEmpty($installerUri.UserInfo) ) if (-not $installerUri -or -not $installerUri.IsAbsoluteUri -or ($installerUri.Scheme -ne "https" -and -not $loopbackHttp) -or -not [string]::IsNullOrEmpty($installerUri.UserInfo)) { Write-Fail "Refusing non-HTTPS installer URL: $installerUrl" exit 1 } # TLS 1.2+ for the fetch (older Windows PowerShell defaults to TLS 1.0, # which GitHub rejects). TLS 1.3 only where schannel can negotiate it; see # the note in install.ps1. $protocols = [Net.SecurityProtocolType]::Tls12 if ([Environment]::OSVersion.Version.Build -ge 20348 -and ([enum]::GetNames([Net.SecurityProtocolType]) -contains 'Tls13')) { $protocols = $protocols -bor [Net.SecurityProtocolType]::Tls13 } [Net.ServicePointManager]::SecurityProtocol = $protocols # A fresh staging directory of its own with an owner-only DACL, the # way install.ps1 stages its own download. The ACL is best effort: a # filesystem that cannot carry one must not fail the install. $stageDir = Join-Path ([System.IO.Path]::GetTempPath()) "cbm-setup-$(Get-Random)" New-Item -ItemType Directory -Path $stageDir -Force | Out-Null try { $stageAcl = New-Object System.Security.AccessControl.DirectorySecurity $stageAcl.SetAccessRuleProtection($true, $false) $stageOwner = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).User $stageAcl.SetOwner($stageOwner) $stageAcl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule( $stageOwner, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow'))) Set-Acl -Path $stageDir -AclObject $stageAcl -ErrorAction Stop } catch { Write-Host " note: could not harden the staging directory ACL: $($_.Exception.Message)" } $installer = Join-Path $stageDir "install.ps1" $failure = $null try { Write-Host "Fetching install.ps1..." -ForegroundColor White Invoke-WebRequest -Uri $installerUrl -OutFile $installer -UseBasicParsing -MaximumRedirection 0 if (-not (Test-Path -LiteralPath $installer -PathType Leaf) -or (Get-Item -LiteralPath $installer).Length -eq 0) { throw "fetched an empty install.ps1 from $installerUrl" } Write-Ok "install.ps1 fetched" Write-Host "" Write-Host "Installing through install.ps1..." -ForegroundColor White # The installer configures nothing (--skip-config): agent configuration # stays this script's interactive step below. It runs in a child host # of the same PowerShell so its exit code is unambiguous and its global # settings do not leak into this session. The pre-seeded exit code # keeps a child that fails to start from reading as success. $hostExe = (Get-Process -Id $PID).Path $global:LASTEXITCODE = 1 & $hostExe -NoProfile -ExecutionPolicy Bypass -File $installer "--dir=$InstallDir" --skip-config if ($LASTEXITCODE -ne 0) { throw "install.ps1 exited with $LASTEXITCODE" } } catch { $failure = "$_" } finally { Remove-Item -Recurse -Force $stageDir -ErrorAction SilentlyContinue } if ($failure) { Write-Fail "Installation through install.ps1 failed: $failure" exit 1 } $binaryPath = Join-Path $InstallDir "$BinaryName.exe" if (-not (Test-Path $binaryPath)) { Write-Fail "Binary not found at $binaryPath after installation" exit 1 } Write-Ok "Installed to $binaryPath" # Verify binary runs try { $verOut = & $binaryPath --version 2>&1 Write-Ok "Version: $verOut" } catch { Write-Warn "Could not verify binary version (may still work)" } # SmartScreen note Write-Host "" Write-Warn "Windows SmartScreen may show a warning when the binary runs for the first time." Write-Host " This is normal for unsigned open-source binaries." -ForegroundColor Yellow Write-Host " Click 'More info' then 'Run anyway' to proceed." -ForegroundColor Yellow Write-Host " Verify checksums at: https://github.com/$Repo/releases" -ForegroundColor Yellow # Check if install dir is on PATH $userPath = [Environment]::GetEnvironmentVariable("Path", "User") if ($userPath -notlike "*$InstallDir*") { Write-Host "" Write-Warn "$InstallDir is not on your PATH." $addPath = Read-Host " Add it to your user PATH? [y/N]" if ($addPath -match '^[Yy]$') { [Environment]::SetEnvironmentVariable("Path", "$userPath;$InstallDir", "User") Write-Ok "Added to user PATH (restart your terminal to take effect)" } } # Configure Claude Code $mcpConfig = [ordered]@{ type = "stdio" command = $binaryPath } Configure-ClaudeCode $mcpConfig Write-Host "" Write-Ok "Done! Restart Claude Code and verify with /mcp" Write-Host "" Write-Host " To uninstall:" -ForegroundColor White Write-Host " Remove-Item -Recurse -Force '$InstallDir'" Write-Host " Remove-Item -Recurse -Force `"$env:LOCALAPPDATA\codebase-memory-mcp`" # graph database" }