diff --git a/README.md b/README.md index b62491d..5e0b095 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,10 @@ We are currently implementing and testing the KiCAD 9.0 IPC API for real-time UI Note: IPC features are under active development and testing. Enable IPC in KiCAD via Preferences > Plugins > Enable IPC API Server. +For OpenCode on Windows, the backend can be configured as `auto`, `ipc`, or +`swig` during setup. See [OpenCode (Windows)](#opencode-windows) for the +configuration command and backend options. + ### Tool Discovery & Router Pattern We've implemented an intelligent tool router to keep AI context efficient while maintaining full functionality: @@ -454,6 +458,7 @@ Choose one: - [Claude Desktop](https://claude.ai/download) - Official Anthropic desktop app - [Claude Code](https://docs.claude.com/claude-code) - Official CLI tool - [Cline](https://github.com/cline/cline) - VSCode extension +- [OpenCode](https://opencode.ai/) - Terminal-based AI coding agent with MCP support ### Supported Platforms @@ -783,6 +788,24 @@ target project: - You need to verify KiCAD Python (`pcbnew`), Node.js, and `dist/index.js` before changing OpenCode configuration +#### Backend selection + +The setup script supports three KiCAD backend preferences via `-Backend`: + +- `auto` - try IPC first and fall back to SWIG if IPC is unavailable (default) +- `ipc` - require KiCAD IPC for real-time UI synchronization +- `swig` - use the file-based `pcbnew` backend + +Examples: + +```powershell +.\setup-windows-opencode.ps1 -Apply -Scope project -Backend auto +.\setup-windows-opencode.ps1 -Apply -Scope project -Backend ipc +.\setup-windows-opencode.ps1 -Apply -Scope project -Backend swig +``` + +For `-Backend ipc`, KiCAD must be running with the IPC API server enabled. + #### Verify setup without changes Use this first when diagnosing installation or path problems. It detects KiCAD, @@ -814,6 +837,7 @@ Example generated OpenCode shape: "LOG_LEVEL": "info", "KICAD_AUTO_LAUNCH": "false", "KICAD_MCP_DEV": "0", + "KICAD_BACKEND": "auto", "PYTHONPATH": "C:\\Program Files\\KiCad\\10.0\\bin\\Lib\\site-packages" }, "enabled": true, diff --git a/requirements.txt b/requirements.txt index f926643..3a9cda3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,11 @@ # KiCAD MCP Server - Python Dependencies # Production dependencies only -# KiCAD Python API (IPC - for future migration) -# kicad-python>=0.5.0 # Uncomment when migrating to IPC API +# KiCAD Python API (IPC / real-time UI sync) +kicad-python>=0.5.0 + +# S-expression parsing used by the MCP runtime +sexpdata>=1.0.2 # Schematic manipulation kicad-skip>=0.1.0 diff --git a/setup-windows-opencode.ps1 b/setup-windows-opencode.ps1 index f638df3..34826fd 100644 --- a/setup-windows-opencode.ps1 +++ b/setup-windows-opencode.ps1 @@ -47,9 +47,17 @@ .PARAMETER ConfigPath Explicit OpenCode config path. Overrides Scope. +.PARAMETER Backend + KiCAD backend preference for the MCP server: auto, ipc, or swig. + auto tries IPC first and falls back to SWIG. ipc requires KiCAD IPC to work. + swig uses the file-based pcbnew backend. + .PARAMETER SkipInstall Skip npm install. +.PARAMETER SkipPythonInstall + Skip installing KiCAD MCP Python dependencies into KiCAD's bundled Python. + .PARAMETER SkipBuild Skip npm run build. @@ -80,7 +88,10 @@ param( [string]$ProjectPath = '', [string]$McpServerPath = '', [string]$ConfigPath = '', + [ValidateSet('auto', 'ipc', 'swig')] + [string]$Backend = 'auto', [switch]$SkipInstall, + [switch]$SkipPythonInstall, [switch]$SkipBuild, [switch]$Force ) @@ -276,7 +287,8 @@ function Set-McpEntry { [System.Collections.IDictionary]$Config, [string]$ServerName, [string]$DistPath, - [string]$PythonPath + [string]$PythonPath, + [string]$Backend ) if (-not $Config.Contains('mcp') -or $null -eq $Config['mcp']) { @@ -288,6 +300,7 @@ function Set-McpEntry { LOG_LEVEL = 'info' KICAD_AUTO_LAUNCH = 'false' KICAD_MCP_DEV = '0' + KICAD_BACKEND = $Backend } if ($PythonPath) { @@ -342,6 +355,10 @@ Write-Host @" $script:Results = [ordered]@{ KiCadFound = $false PcbnewImport = $false + PythonDepsInstalled = $false + PythonDepsImport = $false + KipyImport = $false + IpcConnection = $false NodeFound = $false NpmInstall = $false ProjectBuilt = $false @@ -360,6 +377,7 @@ Write-Info "MCP server path: $McpServerRoot" if ($Scope -eq 'project') { Write-Info "OpenCode project path: $TargetProjectRoot" } +Write-Info "KiCAD backend preference: $Backend" Write-Step 'Step 1: Detecting KiCAD Installation' $kicad = Find-KiCadInstallation -ExplicitRoot $KiCadRoot @@ -390,7 +408,95 @@ if ($kicad) { Write-Warning-Custom 'Skipping pcbnew test because KiCAD was not found.' } -Write-Step 'Step 3: Checking Node.js' +Write-Step 'Step 3: Installing and Testing KiCAD MCP Python Dependencies' +if ($kicad) { + $requirementsPath = Join-Path $McpServerRoot 'requirements.txt' + + try { + $userSiteResult = & $kicad.PythonExe -m site --user-site 2>&1 + if ($LASTEXITCODE -eq 0) { + $userSite = ($userSiteResult | Select-Object -First 1).ToString().Trim() + if ($userSite) { + New-Item -ItemType Directory -Path $userSite -Force | Out-Null + Write-Info "KiCAD Python user site: $userSite" + } + } else { + Write-Warning-Custom "Could not determine KiCAD Python user site: $userSiteResult" + } + } catch { + Write-Warning-Custom "Could not prepare KiCAD Python user site: $($_.Exception.Message)" + } + + if ($SkipPythonInstall) { + Write-Info 'Skipping Python dependency install because -SkipPythonInstall was specified.' + $script:Results.PythonDepsInstalled = $true + } elseif (Test-Path -LiteralPath $requirementsPath) { + Write-Info 'Installing Python packages into KiCAD bundled Python...' + & $kicad.PythonExe -m pip install --user -r $requirementsPath + if ($LASTEXITCODE -eq 0) { + $script:Results.PythonDepsInstalled = $true + Write-Success 'KiCAD MCP Python dependencies installed.' + } else { + Write-Error-Custom 'KiCAD MCP Python dependency installation failed.' + $script:Results.Errors += 'Python dependency install failed' + } + } else { + Write-Error-Custom "requirements.txt was not found: $requirementsPath" + $script:Results.Errors += 'requirements.txt not found' + } + + $depsScript = "import sexpdata, skip, colorlog, pydantic; print('SUCCESS')" + $depsResult = & $kicad.PythonExe -c $depsScript 2>&1 + if ($LASTEXITCODE -eq 0 -and $depsResult -match 'SUCCESS') { + $script:Results.PythonDepsImport = $true + Write-Success 'Core MCP Python dependencies import successfully.' + } else { + Write-Error-Custom 'Core MCP Python dependencies could not be imported.' + Write-Warning-Custom "Output: $depsResult" + $script:Results.Errors += 'Python dependency import failed' + } + + if ($Backend -eq 'swig') { + Write-Info 'Skipping kicad-python / IPC validation because -Backend swig was selected.' + } else { + $kipyScript = "import kipy; print('SUCCESS')" + $kipyResult = & $kicad.PythonExe -c $kipyScript 2>&1 + if ($LASTEXITCODE -eq 0 -and $kipyResult -match 'SUCCESS') { + $script:Results.KipyImport = $true + Write-Success 'kicad-python / kipy imports successfully.' + } else { + $message = 'kicad-python / kipy is not available; IPC real-time sync will fall back to SWIG.' + if ($Backend -eq 'ipc') { + Write-Error-Custom 'kicad-python / kipy is required when -Backend ipc is selected.' + $script:Results.Errors += 'kicad-python / kipy import failed' + } else { + Write-Warning-Custom $message + } + Write-Warning-Custom "Output: $kipyResult" + } + } + + if ($Backend -ne 'swig' -and $script:Results.KipyImport) { + $ipcScript = "from kipy import KiCad; k = KiCad(); k.ping(); print('SUCCESS')" + $ipcResult = & $kicad.PythonExe -c $ipcScript 2>&1 + if ($LASTEXITCODE -eq 0 -and $ipcResult -match 'SUCCESS') { + $script:Results.IpcConnection = $true + Write-Success 'KiCAD IPC connection test passed.' + } else { + if ($Backend -eq 'ipc') { + Write-Error-Custom 'KiCAD IPC connection is required when -Backend ipc is selected.' + $script:Results.Errors += 'KiCAD IPC connection failed' + } else { + Write-Warning-Custom 'KiCAD IPC connection test did not pass. This is expected if KiCAD is closed or IPC is disabled.' + } + Write-Warning-Custom "Output: $ipcResult" + } + } +} else { + Write-Warning-Custom 'Skipping Python dependency setup because KiCAD was not found.' +} + +Write-Step 'Step 4: Checking Node.js' try { $nodeVersion = node --version 2>$null if ($LASTEXITCODE -eq 0) { @@ -408,7 +514,7 @@ try { $script:Results.Errors += 'Node.js not found' } -Write-Step 'Step 4: Installing Node Dependencies' +Write-Step 'Step 5: Installing Node Dependencies' if ($SkipInstall) { Write-Info 'Skipping npm install because -SkipInstall was specified.' $script:Results.NpmInstall = $true @@ -430,7 +536,7 @@ if ($SkipInstall) { Write-Warning-Custom 'Skipping npm install because Node.js was not found.' } -Write-Step 'Step 5: Building TypeScript Project' +Write-Step 'Step 6: Building TypeScript Project' if ($SkipBuild) { Write-Info 'Skipping npm run build because -SkipBuild was specified.' } elseif ($script:Results.NodeFound) { @@ -459,16 +565,19 @@ if (Test-Path -LiteralPath $DistPath) { $script:Results.Errors += 'dist/index.js not found' } -Write-Step 'Step 6: Preparing OpenCode Configuration' +Write-Step 'Step 7: Preparing OpenCode Configuration' $canGenerate = $script:Results.NodeFound -and $script:Results.DistFound if (-not $Force) { - $canGenerate = $canGenerate -and $script:Results.KiCadFound -and $script:Results.PcbnewImport + $canGenerate = $canGenerate -and $script:Results.KiCadFound -and $script:Results.PcbnewImport -and $script:Results.PythonDepsImport + if ($Backend -eq 'ipc') { + $canGenerate = $canGenerate -and $script:Results.KipyImport -and $script:Results.IpcConnection + } } if ($canGenerate) { $config = Read-OpenCodeConfig -Path $TargetConfigPath $pythonPath = if ($kicad) { $kicad.PythonLib } else { '' } - Set-McpEntry -Config $config -ServerName $Name -DistPath $DistPath -PythonPath $pythonPath + Set-McpEntry -Config $config -ServerName $Name -DistPath $DistPath -PythonPath $pythonPath -Backend $Backend $script:Results.ConfigReady = $true Write-Success "OpenCode MCP entry prepared for server name '$Name'." @@ -490,8 +599,11 @@ if ($canGenerate) { } Write-Step 'Setup Summary' +Write-Host " Backend preference: $Backend" Write-Host " KiCAD Installation: $(if ($script:Results.KiCadFound) { '[OK] Found' } else { '[ERROR] Not Found' })" Write-Host " pcbnew Module: $(if ($script:Results.PcbnewImport) { '[OK] Working' } else { '[ERROR] Failed' })" +Write-Host " Python deps: $(if ($script:Results.PythonDepsImport) { '[OK] Working' } elseif ($script:Results.PythonDepsInstalled) { '[WARN] Installed, import failed' } else { '[ERROR] Failed' })" +Write-Host " kicad-python/IPC: $(if ($script:Results.IpcConnection) { '[OK] Connected' } elseif ($script:Results.KipyImport) { '[WARN] Installed, not connected' } else { '[WARN] Not available' })" Write-Host " Node.js: $(if ($script:Results.NodeFound) { '[OK] Found' } else { '[ERROR] Not Found' })" Write-Host " npm install: $(if ($script:Results.NpmInstall) { '[OK] Complete/Skipped' } else { '[WARN] Not Complete' })" Write-Host " TypeScript Build: $(if ($script:Results.ProjectBuilt -or $SkipBuild) { '[OK] Complete/Skipped' } else { '[WARN] Not Complete' })"