fix: Resolve Python executable validation failure on Linux (Issue #29)

This commit fixes the critical bug where the MCP server fails to start on
Linux with "Python executable not found: python3" even when python3 is
correctly installed and available in PATH.

Root Cause:
- findPythonExecutable() returned 'python3' (command name) on Linux
- validatePrerequisites() used existsSync('python3') which checks current
  directory, not PATH
- Validation failed even though spawn() would resolve 'python3' via PATH

Changes Made:

1. Enhanced findPythonExecutable() for Linux (src/server.ts:42-126):
   - Added Linux platform detection
   - Check KiCad bundled Python paths first (/usr/lib/kicad/bin/python3, etc.)
   - Use 'which python3' to resolve system python3 to absolute path
   - Fallback to common system paths (/usr/bin/python3, /bin/python3)
   - Import execSync for 'which' command execution

2. Improved validatePrerequisites() (src/server.ts:214-266):
   - Distinguish between absolute paths and command names
   - Use existsSync for absolute paths
   - Use --version execution test for command names
   - Added Linux-specific error messages and troubleshooting

3. Documentation Updates (README.md:409-444):
   - Added "Linux Python Detection" section
   - Documented detection priority order
   - Added troubleshooting steps for KICAD_PYTHON
   - Clarified that no manual config needed for standard installations

Testing:
- Build completed successfully (npm run build)
- Python detection now resolves /usr/bin/python3 on Ubuntu/Debian
- Maintains backward compatibility with Windows/macOS
- KICAD_PYTHON override still works

Fixes #29

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
KiCAD MCP Bot
2026-01-10 22:23:54 -05:00
parent aa18486359
commit d2723bc292
2 changed files with 121 additions and 10 deletions

View File

@@ -406,6 +406,43 @@ Edit configuration file:
- **Windows:** `C:\Program Files\KiCad\9.0\lib\python3\dist-packages`
- **macOS:** `/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages`
#### Linux Python Detection
The server automatically detects Python on Linux in this priority order:
1. **Virtual environment** - `venv/bin/python` or `.venv/bin/python` (highest priority)
2. **KICAD_PYTHON env var** - User override for non-standard installations
3. **KiCad bundled Python** - `/usr/lib/kicad/bin/python3`, `/usr/local/lib/kicad/bin/python3`, `/opt/kicad/bin/python3`
4. **System Python via which** - Resolves `which python3` to absolute path (e.g., `/usr/bin/python3`)
5. **Common system paths** - `/usr/bin/python3`, `/bin/python3`
**For most standard Linux installations (Ubuntu, Debian, Fedora, Arch), no KICAD_PYTHON configuration is needed** - the server will automatically find your Python installation.
**Troubleshooting:**
If you see "Python executable not found: python3", you can manually specify the Python path:
```json
{
"mcpServers": {
"kicad": {
"command": "node",
"args": ["/path/to/KiCAD-MCP-Server/dist/index.js"],
"env": {
"KICAD_PYTHON": "/usr/bin/python3",
"PYTHONPATH": "/usr/lib/kicad/lib/python3/dist-packages"
}
}
}
}
```
To find your Python path:
```bash
which python3 # Example output: /usr/bin/python3
python3 -c "import pcbnew; print(pcbnew.GetBuildVersion())" # Verify pcbnew access
```
### Cline (VSCode)
Edit: `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`

View File

@@ -5,7 +5,7 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import express from 'express';
import { spawn, exec, ChildProcess } from 'child_process';
import { spawn, exec, execSync, ChildProcess } from 'child_process';
import { existsSync } from 'fs';
import { join, dirname } from 'path';
import { logger } from './logger.js';
@@ -41,6 +41,8 @@ import { registerDesignPrompts } from './prompts/design.js';
*/
function findPythonExecutable(scriptPath: string): string {
const isWindows = process.platform === 'win32';
const isMac = process.platform === 'darwin';
const isLinux = !isWindows && !isMac;
// Get the project root (parent of the python/ directory)
const projectRoot = dirname(dirname(scriptPath));
@@ -65,8 +67,6 @@ function findPythonExecutable(scriptPath: string): string {
}
// Platform-specific KiCAD bundled Python detection
const isMac = process.platform === 'darwin';
if (isWindows && process.env.PYTHONPATH?.includes('KiCad')) {
// Windows: Try KiCAD's bundled Python
const kicadPython = 'C:\\Program Files\\KiCad\\9.0\\bin\\python.exe';
@@ -84,9 +84,43 @@ function findPythonExecutable(scriptPath: string): string {
return kicadPython;
}
}
} else if (isLinux) {
// Linux: Try KiCAD bundled Python locations first
const linuxKicadPaths = [
'/usr/lib/kicad/bin/python3',
'/usr/local/lib/kicad/bin/python3',
'/opt/kicad/bin/python3',
];
for (const path of linuxKicadPaths) {
if (existsSync(path)) {
logger.info(`Found KiCAD bundled Python at: ${path}`);
return path;
}
}
// Default to system Python
// Resolve system python3 to full path using 'which'
try {
const result = execSync('which python3', { encoding: 'utf-8' }).trim();
if (result && existsSync(result)) {
logger.info(`Resolved system Python via which: ${result}`);
return result;
}
} catch (e) {
logger.warn('Failed to resolve python3 via which command');
}
// Fallback to common system paths
const systemPaths = ['/usr/bin/python3', '/bin/python3'];
for (const path of systemPaths) {
if (existsSync(path)) {
logger.info(`Found system Python at: ${path}`);
return path;
}
}
}
// Default to system Python (last resort)
logger.info('Using system Python (no venv found)');
return isWindows ? 'python.exe' : 'python3';
}
@@ -179,15 +213,55 @@ export class KiCADMcpServer {
*/
private async validatePrerequisites(pythonExe: string): Promise<boolean> {
const isWindows = process.platform === 'win32';
const isLinux = process.platform !== 'win32' && process.platform !== 'darwin';
const errors: string[] = [];
// Check if Python executable exists
// Check if Python executable exists (for absolute paths) or is executable (for commands)
const isAbsolutePath = pythonExe.startsWith('/') || pythonExe.startsWith('C:') || pythonExe.startsWith('\\');
if (isAbsolutePath) {
// Absolute path: use existsSync
if (!existsSync(pythonExe)) {
errors.push(`Python executable not found: ${pythonExe}`);
if (isWindows) {
errors.push('Windows: Install KiCAD 9.0+ from https://www.kicad.org/download/windows/');
errors.push('Or run: .\\setup-windows.ps1 for automatic configuration');
} else if (isLinux) {
errors.push('Linux: Install KiCAD 9.0+ or set KICAD_PYTHON environment variable');
errors.push('Set KICAD_PYTHON to specify a custom Python path');
}
}
} else {
// Command name: verify it's executable via --version test
logger.info(`Validating command-based Python executable: ${pythonExe}`);
try {
const { stdout } = await new Promise<{stdout: string, stderr: string}>((resolve, reject) => {
exec(`"${pythonExe}" --version`, {
timeout: 3000,
env: { ...process.env }
}, (error: any, stdout: string, stderr: string) => {
if (error) {
reject(error);
} else {
resolve({ stdout, stderr });
}
});
});
logger.info(`Python version check passed: ${stdout.trim()}`);
} catch (error: any) {
errors.push(`Python executable not found in PATH: ${pythonExe}`);
errors.push(`Error: ${error.message}`);
errors.push('Set KICAD_PYTHON environment variable to specify full path');
if (isLinux) {
errors.push('');
errors.push('Linux troubleshooting:');
errors.push('1. Check if python3 is installed: which python3');
errors.push('2. Install KiCAD: sudo apt install kicad (Ubuntu/Debian)');
errors.push('3. Set KICAD_PYTHON=/usr/bin/python3 in your MCP config');
}
}
}