Skip to content

Instantly share code, notes, and snippets.

@jongalloway
Last active September 7, 2026 03:37
Show Gist options
  • Select an option

  • Save jongalloway/9a981f072e957ab972a8b8098c6bc706 to your computer and use it in GitHub Desktop.

Select an option

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')
#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