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:
37
README.md
37
README.md
@@ -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`
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
if (!existsSync(pythonExe)) {
|
||||
errors.push(`Python executable not found: ${pythonExe}`);
|
||||
// Check if Python executable exists (for absolute paths) or is executable (for commands)
|
||||
const isAbsolutePath = pythonExe.startsWith('/') || pythonExe.startsWith('C:') || pythonExe.startsWith('\\');
|
||||
|
||||
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');
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user