Merge pull request #205 from romybompart/feat/opencode-backend-selection

feat: improve OpenCode KiCad setup and backend selection
This commit is contained in:
mixelpixx
2026-05-30 09:36:02 -04:00
committed by GitHub
3 changed files with 148 additions and 9 deletions

View File

@@ -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. 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 ### Tool Discovery & Router Pattern
We've implemented an intelligent tool router to keep AI context efficient while maintaining full functionality: 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 Desktop](https://claude.ai/download) - Official Anthropic desktop app
- [Claude Code](https://docs.claude.com/claude-code) - Official CLI tool - [Claude Code](https://docs.claude.com/claude-code) - Official CLI tool
- [Cline](https://github.com/cline/cline) - VSCode extension - [Cline](https://github.com/cline/cline) - VSCode extension
- [OpenCode](https://opencode.ai/) - Terminal-based AI coding agent with MCP support
### Supported Platforms ### Supported Platforms
@@ -783,6 +788,24 @@ target project:
- You need to verify KiCAD Python (`pcbnew`), Node.js, and `dist/index.js` - You need to verify KiCAD Python (`pcbnew`), Node.js, and `dist/index.js`
before changing OpenCode configuration 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 #### Verify setup without changes
Use this first when diagnosing installation or path problems. It detects KiCAD, Use this first when diagnosing installation or path problems. It detects KiCAD,
@@ -814,6 +837,7 @@ Example generated OpenCode shape:
"LOG_LEVEL": "info", "LOG_LEVEL": "info",
"KICAD_AUTO_LAUNCH": "false", "KICAD_AUTO_LAUNCH": "false",
"KICAD_MCP_DEV": "0", "KICAD_MCP_DEV": "0",
"KICAD_BACKEND": "auto",
"PYTHONPATH": "C:\\Program Files\\KiCad\\10.0\\bin\\Lib\\site-packages" "PYTHONPATH": "C:\\Program Files\\KiCad\\10.0\\bin\\Lib\\site-packages"
}, },
"enabled": true, "enabled": true,

View File

@@ -1,8 +1,11 @@
# KiCAD MCP Server - Python Dependencies # KiCAD MCP Server - Python Dependencies
# Production dependencies only # Production dependencies only
# KiCAD Python API (IPC - for future migration) # KiCAD Python API (IPC / real-time UI sync)
# kicad-python>=0.5.0 # Uncomment when migrating to IPC API kicad-python>=0.5.0
# S-expression parsing used by the MCP runtime
sexpdata>=1.0.2
# Schematic manipulation # Schematic manipulation
kicad-skip>=0.1.0 kicad-skip>=0.1.0

View File

@@ -47,9 +47,17 @@
.PARAMETER ConfigPath .PARAMETER ConfigPath
Explicit OpenCode config path. Overrides Scope. 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 .PARAMETER SkipInstall
Skip npm install. Skip npm install.
.PARAMETER SkipPythonInstall
Skip installing KiCAD MCP Python dependencies into KiCAD's bundled Python.
.PARAMETER SkipBuild .PARAMETER SkipBuild
Skip npm run build. Skip npm run build.
@@ -80,7 +88,10 @@ param(
[string]$ProjectPath = '', [string]$ProjectPath = '',
[string]$McpServerPath = '', [string]$McpServerPath = '',
[string]$ConfigPath = '', [string]$ConfigPath = '',
[ValidateSet('auto', 'ipc', 'swig')]
[string]$Backend = 'auto',
[switch]$SkipInstall, [switch]$SkipInstall,
[switch]$SkipPythonInstall,
[switch]$SkipBuild, [switch]$SkipBuild,
[switch]$Force [switch]$Force
) )
@@ -276,7 +287,8 @@ function Set-McpEntry {
[System.Collections.IDictionary]$Config, [System.Collections.IDictionary]$Config,
[string]$ServerName, [string]$ServerName,
[string]$DistPath, [string]$DistPath,
[string]$PythonPath [string]$PythonPath,
[string]$Backend
) )
if (-not $Config.Contains('mcp') -or $null -eq $Config['mcp']) { if (-not $Config.Contains('mcp') -or $null -eq $Config['mcp']) {
@@ -288,6 +300,7 @@ function Set-McpEntry {
LOG_LEVEL = 'info' LOG_LEVEL = 'info'
KICAD_AUTO_LAUNCH = 'false' KICAD_AUTO_LAUNCH = 'false'
KICAD_MCP_DEV = '0' KICAD_MCP_DEV = '0'
KICAD_BACKEND = $Backend
} }
if ($PythonPath) { if ($PythonPath) {
@@ -342,6 +355,10 @@ Write-Host @"
$script:Results = [ordered]@{ $script:Results = [ordered]@{
KiCadFound = $false KiCadFound = $false
PcbnewImport = $false PcbnewImport = $false
PythonDepsInstalled = $false
PythonDepsImport = $false
KipyImport = $false
IpcConnection = $false
NodeFound = $false NodeFound = $false
NpmInstall = $false NpmInstall = $false
ProjectBuilt = $false ProjectBuilt = $false
@@ -360,6 +377,7 @@ Write-Info "MCP server path: $McpServerRoot"
if ($Scope -eq 'project') { if ($Scope -eq 'project') {
Write-Info "OpenCode project path: $TargetProjectRoot" Write-Info "OpenCode project path: $TargetProjectRoot"
} }
Write-Info "KiCAD backend preference: $Backend"
Write-Step 'Step 1: Detecting KiCAD Installation' Write-Step 'Step 1: Detecting KiCAD Installation'
$kicad = Find-KiCadInstallation -ExplicitRoot $KiCadRoot $kicad = Find-KiCadInstallation -ExplicitRoot $KiCadRoot
@@ -390,7 +408,95 @@ if ($kicad) {
Write-Warning-Custom 'Skipping pcbnew test because KiCAD was not found.' 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 { try {
$nodeVersion = node --version 2>$null $nodeVersion = node --version 2>$null
if ($LASTEXITCODE -eq 0) { if ($LASTEXITCODE -eq 0) {
@@ -408,7 +514,7 @@ try {
$script:Results.Errors += 'Node.js not found' $script:Results.Errors += 'Node.js not found'
} }
Write-Step 'Step 4: Installing Node Dependencies' Write-Step 'Step 5: Installing Node Dependencies'
if ($SkipInstall) { if ($SkipInstall) {
Write-Info 'Skipping npm install because -SkipInstall was specified.' Write-Info 'Skipping npm install because -SkipInstall was specified.'
$script:Results.NpmInstall = $true $script:Results.NpmInstall = $true
@@ -430,7 +536,7 @@ if ($SkipInstall) {
Write-Warning-Custom 'Skipping npm install because Node.js was not found.' 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) { if ($SkipBuild) {
Write-Info 'Skipping npm run build because -SkipBuild was specified.' Write-Info 'Skipping npm run build because -SkipBuild was specified.'
} elseif ($script:Results.NodeFound) { } elseif ($script:Results.NodeFound) {
@@ -459,16 +565,19 @@ if (Test-Path -LiteralPath $DistPath) {
$script:Results.Errors += 'dist/index.js not found' $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 $canGenerate = $script:Results.NodeFound -and $script:Results.DistFound
if (-not $Force) { 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) { if ($canGenerate) {
$config = Read-OpenCodeConfig -Path $TargetConfigPath $config = Read-OpenCodeConfig -Path $TargetConfigPath
$pythonPath = if ($kicad) { $kicad.PythonLib } else { '' } $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 $script:Results.ConfigReady = $true
Write-Success "OpenCode MCP entry prepared for server name '$Name'." Write-Success "OpenCode MCP entry prepared for server name '$Name'."
@@ -490,8 +599,11 @@ if ($canGenerate) {
} }
Write-Step 'Setup Summary' 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 " 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 " 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 " 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 " 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' })" Write-Host " TypeScript Build: $(if ($script:Results.ProjectBuilt -or $SkipBuild) { '[OK] Complete/Skipped' } else { '[WARN] Not Complete' })"