Last active
September 7, 2026 03:37
-
-
Save jongalloway/9a981f072e957ab972a8b8098c6bc706 to your computer and use it in GitHub Desktop.
Windows bootstrap for aravindev/inkscape_mcp: portable D-Bus, Python 3.12 via uvx, optional package-feed proxy, and GitHub Copilot CLI registration. Run using iex (irm 'https://gist.githubusercontent.com/jongalloway/9a981f072e957ab972a8b8098c6bc706/raw/ Install-InkscapeMcpWindows.ps1')
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #Requires -Version 5.1 | |
| <# | |
| .SYNOPSIS | |
| Installs and configures Inkscape MCP for GitHub Copilot CLI on Windows. | |
| .DESCRIPTION | |
| Windows bootstrap for https://github.com/aravindev/inkscape_mcp | |
| The script installs a small, portable D-Bus session daemon from the official | |
| MSYS2 package without installing MSYS2. It then registers the pinned Inkscape | |
| MCP release with GitHub Copilot CLI using Python 3.12 through uvx. | |
| No administrator access is required. The installation is user-scoped and does | |
| not modify PATH. | |
| Run from PowerShell: | |
| Set-ExecutionPolicy -Scope Process Bypass | |
| .\Install-InkscapeMcpWindows.ps1 | |
| If your environment uses a Python package-feed proxy: | |
| .\Install-InkscapeMcpWindows.ps1 -PackageIndex https://proxy.example/pypi/simple | |
| The package index is resolved in this order: | |
| 1. -PackageIndex | |
| 2. UV_DEFAULT_INDEX | |
| 3. PACKAGE_FEED_PROXY (with /pypi/simple appended when needed) | |
| 4. uv's normal public PyPI default | |
| .PARAMETER InstallDirectory | |
| Portable D-Bus destination. Defaults to | |
| %LOCALAPPDATA%\InkscapeMCP\dbus. | |
| .PARAMETER InkscapePath | |
| Optional explicit path to inkscape.exe. The script otherwise checks | |
| INKSCAPE_BIN, PATH, and the standard Program Files locations. | |
| .PARAMETER PackageIndex | |
| Optional Python simple-index URL passed only to the registered MCP process. | |
| Public users normally should not set this. | |
| .PARAMETER McpVersion | |
| Pinned inkscape_mcp release to install from the project's GitHub releases. | |
| .PARAMETER SkipMcpRegistration | |
| Installs and tests D-Bus but does not modify Copilot CLI MCP configuration. | |
| .PARAMETER SkipEnvironmentVariable | |
| Does not persist INKSCAPE_MCP_DBUS_DAEMON. Intended for testing. | |
| .PARAMETER Force | |
| Reinstalls D-Bus even when the expected daemon already exists. | |
| .PARAMETER Uninstall | |
| Removes the portable D-Bus files, its user environment variable, and the | |
| inkscape_mcp Copilot registration. | |
| .EXAMPLE | |
| .\Install-InkscapeMcpWindows.ps1 | |
| .EXAMPLE | |
| .\Install-InkscapeMcpWindows.ps1 -PackageIndex $env:MY_PYPI_INDEX | |
| .EXAMPLE | |
| .\Install-InkscapeMcpWindows.ps1 -Uninstall | |
| #> | |
| [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] | |
| param( | |
| [string] $InstallDirectory = (Join-Path $env:LOCALAPPDATA 'InkscapeMCP\dbus'), | |
| [string] $InkscapePath, | |
| [string] $PackageIndex, | |
| [string] $McpVersion = '1.3.1', | |
| [switch] $SkipMcpRegistration, | |
| [switch] $SkipEnvironmentVariable, | |
| [switch] $Force, | |
| [switch] $Uninstall | |
| ) | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| # Pinned metadata from https://packages.msys2.org/packages/mingw-w64-x86_64-dbus | |
| $DbusPackageVersion = '1.16.2-4' | |
| $DbusPackageFile = "mingw-w64-x86_64-dbus-$DbusPackageVersion-any.pkg.tar.zst" | |
| $DbusPackageUri = "https://repo.msys2.org/mingw/mingw64/$DbusPackageFile" | |
| $DbusPackageSha256 = '23ca4fd21683f7e482a4f6a950eb05c9d68fc923c4691880bc0afbe5ddf2bd68' | |
| $McpWheelUri = ( | |
| "https://github.com/aravindev/inkscape_mcp/releases/download/v$McpVersion/" + | |
| "inkscape_mcp-$McpVersion-py3-none-any.whl" | |
| ) | |
| $DbusEnvironmentVariable = 'INKSCAPE_MCP_DBUS_DAEMON' | |
| $McpServerName = 'inkscape_mcp' | |
| $InstallDirectory = [IO.Path]::GetFullPath($InstallDirectory) | |
| $DaemonPath = Join-Path $InstallDirectory 'bin\dbus-daemon.exe' | |
| function Remove-TemporaryDirectory { | |
| param([string] $Path) | |
| if ($Path -and (Test-Path -LiteralPath $Path)) { | |
| Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| function Resolve-InkscapeExecutable { | |
| param([string] $ExplicitPath) | |
| $candidates = @( | |
| $ExplicitPath, | |
| $env:INKSCAPE_BIN, | |
| (Join-Path $env:ProgramFiles 'Inkscape\bin\inkscape.exe'), | |
| (Join-Path $env:ProgramFiles 'Inkscape\inkscape.exe') | |
| ) | |
| $command = Get-Command inkscape.exe -ErrorAction SilentlyContinue | |
| if ($command) { | |
| $candidates += $command.Source | |
| } | |
| foreach ($candidate in $candidates) { | |
| if ($candidate -and (Test-Path -LiteralPath $candidate -PathType Leaf)) { | |
| return [IO.Path]::GetFullPath($candidate) | |
| } | |
| } | |
| return $null | |
| } | |
| function Resolve-UvxExecutable { | |
| $command = Get-Command uvx.exe -ErrorAction SilentlyContinue | |
| if ($command) { | |
| return $command.Source | |
| } | |
| $wingetPackages = Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages' | |
| if (Test-Path -LiteralPath $wingetPackages) { | |
| $candidate = Get-ChildItem ` | |
| -Path (Join-Path $wingetPackages 'astral-sh.uv_Microsoft.Winget.Source_*') ` | |
| -Filter uvx.exe ` | |
| -Recurse ` | |
| -ErrorAction SilentlyContinue | | |
| Select-Object -First 1 | |
| if ($candidate) { | |
| return $candidate.FullName | |
| } | |
| } | |
| return $null | |
| } | |
| function Resolve-PythonPackageIndex { | |
| param([string] $ExplicitIndex) | |
| if ($ExplicitIndex) { | |
| return $ExplicitIndex.TrimEnd('/') | |
| } | |
| if ($env:UV_DEFAULT_INDEX) { | |
| return $env:UV_DEFAULT_INDEX.TrimEnd('/') | |
| } | |
| if ($env:PACKAGE_FEED_PROXY) { | |
| $proxy = $env:PACKAGE_FEED_PROXY.TrimEnd('/') | |
| if ($proxy -match '/pypi/simple$') { | |
| return $proxy | |
| } | |
| return "$proxy/pypi/simple" | |
| } | |
| return $null | |
| } | |
| function Test-DbusDaemon { | |
| param([Parameter(Mandatory)][string] $Path) | |
| $startInfo = [Diagnostics.ProcessStartInfo]::new() | |
| $startInfo.FileName = $Path | |
| $startInfo.Arguments = '--session --print-address=1 --nofork' | |
| $startInfo.UseShellExecute = $false | |
| $startInfo.CreateNoWindow = $true | |
| $startInfo.RedirectStandardOutput = $true | |
| $startInfo.RedirectStandardError = $true | |
| $process = [Diagnostics.Process]::new() | |
| $process.StartInfo = $startInfo | |
| try { | |
| if (-not $process.Start()) { | |
| throw 'The process did not start.' | |
| } | |
| $addressTask = $process.StandardOutput.ReadLineAsync() | |
| if (-not $addressTask.Wait(10000)) { | |
| $errorText = $process.StandardError.ReadToEnd() | |
| throw "Timed out waiting for the daemon to publish its address. $errorText" | |
| } | |
| $address = $addressTask.Result | |
| if ([string]::IsNullOrWhiteSpace($address) -or $address -notmatch 'guid=') { | |
| $errorText = $process.StandardError.ReadToEnd() | |
| throw "The daemon did not publish a valid bus address. Output: '$address'. $errorText" | |
| } | |
| Write-Verbose "Temporary D-Bus address: $address" | |
| } | |
| finally { | |
| if ($process -and -not $process.HasExited) { | |
| $process.Kill() | |
| $process.WaitForExit(5000) | Out-Null | |
| } | |
| if ($process) { | |
| $process.Dispose() | |
| } | |
| } | |
| } | |
| function Install-PortableDbus { | |
| param( | |
| [Parameter(Mandatory)][string] $Destination, | |
| [Parameter(Mandatory)][string] $Executable | |
| ) | |
| if ((Test-Path -LiteralPath $Executable) -and -not $Force) { | |
| Write-Host "D-Bus is already installed at $Executable" | |
| Test-DbusDaemon -Path $Executable | |
| return | |
| } | |
| if (-not $PSCmdlet.ShouldProcess($Destination, "Install portable D-Bus $DbusPackageVersion")) { | |
| return | |
| } | |
| $tar = Get-Command tar.exe -ErrorAction SilentlyContinue | |
| if (-not $tar) { | |
| throw 'Windows tar.exe was not found. Use a current Windows 10/11 release or install bsdtar.' | |
| } | |
| $tempDirectory = Join-Path ([IO.Path]::GetTempPath()) ( | |
| 'inkscape-mcp-dbus-' + [guid]::NewGuid().ToString('N') | |
| ) | |
| $archivePath = Join-Path $tempDirectory $DbusPackageFile | |
| $extractDirectory = Join-Path $tempDirectory 'extract' | |
| $backupDirectory = "$Destination.backup" | |
| $installationReplaced = $false | |
| try { | |
| New-Item -ItemType Directory -Path $extractDirectory -Force | Out-Null | |
| Write-Host "Downloading D-Bus $DbusPackageVersion from the official MSYS2 repository..." | |
| Invoke-WebRequest -Uri $DbusPackageUri -OutFile $archivePath -UseBasicParsing | |
| $actualHash = (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash.ToLowerInvariant() | |
| if ($actualHash -ne $DbusPackageSha256) { | |
| throw ( | |
| "Package checksum mismatch. Expected $DbusPackageSha256 but received " + | |
| "$actualHash. No files were installed." | |
| ) | |
| } | |
| Write-Host 'Package checksum verified.' | |
| & $tar.Source -xf $archivePath -C $extractDirectory | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "tar.exe could not extract the package (exit code $LASTEXITCODE)." | |
| } | |
| $packageRoot = Join-Path $extractDirectory 'mingw64' | |
| $extractedDaemon = Join-Path $packageRoot 'bin\dbus-daemon.exe' | |
| if (-not (Test-Path -LiteralPath $extractedDaemon -PathType Leaf)) { | |
| throw 'The verified package did not contain the expected dbus-daemon.exe.' | |
| } | |
| if (Test-Path -LiteralPath $backupDirectory) { | |
| Remove-Item -LiteralPath $backupDirectory -Recurse -Force | |
| } | |
| if (Test-Path -LiteralPath $Destination) { | |
| Move-Item -LiteralPath $Destination -Destination $backupDirectory | |
| } | |
| try { | |
| New-Item -ItemType Directory -Path $Destination -Force | Out-Null | |
| Copy-Item -Path (Join-Path $packageRoot '*') -Destination $Destination -Recurse -Force | |
| $installationReplaced = $true | |
| } | |
| catch { | |
| Remove-Item -LiteralPath $Destination -Recurse -Force -ErrorAction SilentlyContinue | |
| if (Test-Path -LiteralPath $backupDirectory) { | |
| Move-Item -LiteralPath $backupDirectory -Destination $Destination | |
| } | |
| throw | |
| } | |
| Test-DbusDaemon -Path $Executable | |
| Write-Host 'D-Bus session daemon started successfully during verification.' | |
| if (Test-Path -LiteralPath $backupDirectory) { | |
| Remove-Item -LiteralPath $backupDirectory -Recurse -Force | |
| } | |
| Write-Host "Installed portable D-Bus to $Destination" | |
| } | |
| catch { | |
| if ($installationReplaced) { | |
| Remove-Item -LiteralPath $Destination -Recurse -Force -ErrorAction SilentlyContinue | |
| if (Test-Path -LiteralPath $backupDirectory) { | |
| Move-Item ` | |
| -LiteralPath $backupDirectory ` | |
| -Destination $Destination ` | |
| -ErrorAction SilentlyContinue | |
| } | |
| } | |
| throw | |
| } | |
| finally { | |
| Remove-TemporaryDirectory -Path $tempDirectory | |
| } | |
| } | |
| function Remove-CopilotRegistration { | |
| param([string] $CopilotExecutable) | |
| if (-not $CopilotExecutable) { | |
| return | |
| } | |
| $list = (& $CopilotExecutable mcp list 2>&1 | Out-String) | |
| if ($list -match "(?m)^\s*$([regex]::Escape($McpServerName))\s+\(local\)") { | |
| & $CopilotExecutable mcp remove $McpServerName | Out-Host | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Could not remove the existing '$McpServerName' MCP registration." | |
| } | |
| } | |
| } | |
| function Register-CopilotMcp { | |
| param( | |
| [Parameter(Mandatory)][string] $CopilotExecutable, | |
| [Parameter(Mandatory)][string] $UvxExecutable, | |
| [Parameter(Mandatory)][string] $InkscapeExecutable, | |
| [string] $ResolvedPackageIndex | |
| ) | |
| if (-not $PSCmdlet.ShouldProcess('GitHub Copilot CLI user configuration', "Register $McpServerName")) { | |
| return | |
| } | |
| Remove-CopilotRegistration -CopilotExecutable $CopilotExecutable | |
| $arguments = @( | |
| 'mcp', 'add', $McpServerName, | |
| '--timeout', '120000', | |
| '--env', "$DbusEnvironmentVariable=$DaemonPath", | |
| '--env', "INKSCAPE_BIN=$InkscapeExecutable" | |
| ) | |
| if ($ResolvedPackageIndex) { | |
| $arguments += @('--env', "UV_DEFAULT_INDEX=$ResolvedPackageIndex") | |
| } | |
| $arguments += @('--', $UvxExecutable, '--python', '3.12', $McpWheelUri) | |
| & $CopilotExecutable @arguments | Out-Host | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Could not register the '$McpServerName' MCP server." | |
| } | |
| Write-Host "Registered $McpServerName with GitHub Copilot CLI." | |
| if ($ResolvedPackageIndex) { | |
| Write-Host "Configured its Python package index as $ResolvedPackageIndex" | |
| } | |
| else { | |
| Write-Host 'No package-index override was configured; uv will use its public default.' | |
| } | |
| } | |
| $copilotCommand = Get-Command copilot.exe -ErrorAction SilentlyContinue | |
| if ($Uninstall) { | |
| if (-not $PSCmdlet.ShouldProcess($InstallDirectory, 'Uninstall Inkscape MCP Windows bootstrap')) { | |
| return | |
| } | |
| if (-not $SkipMcpRegistration -and $copilotCommand) { | |
| Remove-CopilotRegistration -CopilotExecutable $copilotCommand.Source | |
| } | |
| $configuredPath = [Environment]::GetEnvironmentVariable($DbusEnvironmentVariable, 'User') | |
| if ($configuredPath -and [IO.Path]::GetFullPath($configuredPath) -eq $DaemonPath) { | |
| [Environment]::SetEnvironmentVariable($DbusEnvironmentVariable, $null, 'User') | |
| Remove-Item "Env:$DbusEnvironmentVariable" -ErrorAction SilentlyContinue | |
| Write-Host "Cleared user environment variable $DbusEnvironmentVariable." | |
| } | |
| if (Test-Path -LiteralPath $InstallDirectory) { | |
| Remove-Item -LiteralPath $InstallDirectory -Recurse -Force | |
| Write-Host "Removed $InstallDirectory" | |
| } | |
| else { | |
| Write-Host 'No portable D-Bus installation was found at the requested location.' | |
| } | |
| return | |
| } | |
| $resolvedInkscape = Resolve-InkscapeExecutable -ExplicitPath $InkscapePath | |
| if (-not $resolvedInkscape) { | |
| Write-Warning 'Inkscape is not installed or could not be located.' | |
| Write-Host 'Install it, then rerun this script:' | |
| Write-Host ' winget install --id Inkscape.Inkscape --exact --source winget' | |
| throw 'Inkscape is required.' | |
| } | |
| Write-Host "Found Inkscape: $resolvedInkscape" | |
| Install-PortableDbus -Destination $InstallDirectory -Executable $DaemonPath | |
| if (-not $SkipEnvironmentVariable) { | |
| [Environment]::SetEnvironmentVariable($DbusEnvironmentVariable, $DaemonPath, 'User') | |
| Set-Item "Env:$DbusEnvironmentVariable" $DaemonPath | |
| Write-Host "Configured user environment variable $DbusEnvironmentVariable." | |
| } | |
| if (-not $SkipMcpRegistration) { | |
| if (-not $copilotCommand) { | |
| Write-Warning 'GitHub Copilot CLI was not found.' | |
| Write-Host 'Install it using the official instructions, then rerun this script:' | |
| Write-Host ' https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli' | |
| throw 'GitHub Copilot CLI is required unless -SkipMcpRegistration is used.' | |
| } | |
| $uvx = Resolve-UvxExecutable | |
| if (-not $uvx) { | |
| Write-Warning 'uv/uvx is not installed.' | |
| Write-Host 'Install it, open a new PowerShell window, then rerun this script:' | |
| Write-Host ' winget install --id astral-sh.uv --exact --scope user --source winget' | |
| throw 'uvx is required for MCP registration.' | |
| } | |
| $resolvedIndex = Resolve-PythonPackageIndex -ExplicitIndex $PackageIndex | |
| Register-CopilotMcp ` | |
| -CopilotExecutable $copilotCommand.Source ` | |
| -UvxExecutable $uvx ` | |
| -InkscapeExecutable $resolvedInkscape ` | |
| -ResolvedPackageIndex $resolvedIndex | |
| } | |
| Write-Host '' | |
| Write-Host 'Inkscape MCP Windows setup completed successfully.' | |
| Write-Host 'Restart GitHub Copilot CLI before using the newly registered MCP server.' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment