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`
|
- **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`
|
- **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)
|
### Cline (VSCode)
|
||||||
|
|
||||||
Edit: `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
|
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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { spawn, exec, ChildProcess } from 'child_process';
|
import { spawn, exec, execSync, ChildProcess } from 'child_process';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import { logger } from './logger.js';
|
import { logger } from './logger.js';
|
||||||
@@ -41,6 +41,8 @@ import { registerDesignPrompts } from './prompts/design.js';
|
|||||||
*/
|
*/
|
||||||
function findPythonExecutable(scriptPath: string): string {
|
function findPythonExecutable(scriptPath: string): string {
|
||||||
const isWindows = process.platform === 'win32';
|
const isWindows = process.platform === 'win32';
|
||||||
|
const isMac = process.platform === 'darwin';
|
||||||
|
const isLinux = !isWindows && !isMac;
|
||||||
|
|
||||||
// Get the project root (parent of the python/ directory)
|
// Get the project root (parent of the python/ directory)
|
||||||
const projectRoot = dirname(dirname(scriptPath));
|
const projectRoot = dirname(dirname(scriptPath));
|
||||||
@@ -65,8 +67,6 @@ function findPythonExecutable(scriptPath: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Platform-specific KiCAD bundled Python detection
|
// Platform-specific KiCAD bundled Python detection
|
||||||
const isMac = process.platform === 'darwin';
|
|
||||||
|
|
||||||
if (isWindows && process.env.PYTHONPATH?.includes('KiCad')) {
|
if (isWindows && process.env.PYTHONPATH?.includes('KiCad')) {
|
||||||
// Windows: Try KiCAD's bundled Python
|
// Windows: Try KiCAD's bundled Python
|
||||||
const kicadPython = 'C:\\Program Files\\KiCad\\9.0\\bin\\python.exe';
|
const kicadPython = 'C:\\Program Files\\KiCad\\9.0\\bin\\python.exe';
|
||||||
@@ -84,9 +84,43 @@ function findPythonExecutable(scriptPath: string): string {
|
|||||||
return kicadPython;
|
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)');
|
logger.info('Using system Python (no venv found)');
|
||||||
return isWindows ? 'python.exe' : 'python3';
|
return isWindows ? 'python.exe' : 'python3';
|
||||||
}
|
}
|
||||||
@@ -179,15 +213,55 @@ export class KiCADMcpServer {
|
|||||||
*/
|
*/
|
||||||
private async validatePrerequisites(pythonExe: string): Promise<boolean> {
|
private async validatePrerequisites(pythonExe: string): Promise<boolean> {
|
||||||
const isWindows = process.platform === 'win32';
|
const isWindows = process.platform === 'win32';
|
||||||
|
const isLinux = process.platform !== 'win32' && process.platform !== 'darwin';
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
// Check if Python executable exists
|
// Check if Python executable exists (for absolute paths) or is executable (for commands)
|
||||||
if (!existsSync(pythonExe)) {
|
const isAbsolutePath = pythonExe.startsWith('/') || pythonExe.startsWith('C:') || pythonExe.startsWith('\\');
|
||||||
errors.push(`Python executable not found: ${pythonExe}`);
|
|
||||||
|
|
||||||
if (isWindows) {
|
if (isAbsolutePath) {
|
||||||
errors.push('Windows: Install KiCAD 9.0+ from https://www.kicad.org/download/windows/');
|
// Absolute path: use existsSync
|
||||||
errors.push('Or run: .\\setup-windows.ps1 for automatic configuration');
|
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