Skip to main content
Glama
python-path-support.md7.29 kB
# Python Interpreter Selection (pythonPath Support) ## 概要 Debug-MCPは `pythonPath` パラメータを使用して、デバッグセッションで使用するPythonインタープリタを指定できます。これにより、異なるバージョンのPythonや仮想環境のインタープリタを使ってスクリプトをデバッグできます。 ## 問題の背景 ### 発生していた問題 別のリポジトリでデバッグを試みた際、以下のエラーが発生: ``` AttributeError: 'PosixPath' object has no attribute '_str' ``` ### 原因 1. **Python バージョンの不一致** - デバッグサーバーと対象リポジトリが異なるPythonバージョンを使用 - `multiprocessing.Process` は親プロセスと同じインタープリタを強制使用 - 異なるバージョン間で `pathlib.Path` オブジェクトをpickleで転送すると内部構造の不一致により破損 2. **内部実装の詳細** - `PosixPath._str` はPython 3.11で追加されたスロット - 古いバージョンで作成された Path を新しいバージョンで読むと属性不足でエラー ## 解決方法 ### アーキテクチャの変更 **以前**: `multiprocessing.Process` - 親と同じPythonインタープリタを使用(変更不可) - IPC: `multiprocessing.Pipe`(pickle ベース) **現在**: `subprocess.Popen` - 任意のPythonインタープリタを指定可能 - IPC: stdin/stdout による JSON Lines(バージョン非依存) ### 実装の詳細 #### 1. Runner のスタンドアロン化 `runner_main.py` を作成し、独立したスクリプトとして実行可能に: ```python # src/mcp_debug_tool/runner_main.py python_executable = session.python_path or sys.executable runner_script = Path(__file__).parent / "runner_main.py" process = subprocess.Popen( [python_executable, str(runner_script), str(workspace_root)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, ) ``` #### 2. JSON Lines による IPC pickle の代わりに JSON Lines を使用: ```python # 送信 command_json = json.dumps(command) + '\n' process.stdin.write(command_json) process.stdin.flush() # 受信 response_line = process.stdout.readline() response_data = json.loads(response_line) ``` #### 3. Path オブジェクトの安全な変換 サブプロセス内で Path を文字列に変換: ```python def _convert_paths_to_str(obj): """Recursively convert Path objects to strings.""" if isinstance(obj, Path): import os try: return os.fspath(obj) except (TypeError, AttributeError): # Fallback for broken Path objects if hasattr(obj, 'parts'): return '/'.join(obj.parts) if obj.parts else '' return '<invalid path object>' # ... 再帰的に dict/list を処理 ``` ## 使用方法 ### 1. デフォルト(現在のPython) ```json { "entry": "main.py" } ``` 現在のプロセスと同じPythonインタープリタを使用。 ### 2. 明示的なパス指定 ```json { "entry": "main.py", "pythonPath": "/usr/local/bin/python3.11" } ``` 指定したインタープリタでデバッグセッションを実行。 ### 3. 仮想環境のPython ```json { "entry": "main.py", "pythonPath": "/path/to/project/.venv/bin/python" } ``` プロジェクト固有の仮想環境を使用。 ### 4. pyenv で管理されたPython ```bash # pyenvで使用するバージョンを確認 pyenv which python # 例: /Users/user/.pyenv/versions/3.11.9/bin/python # MCP クライアントから指定 { "entry": "app.py", "pythonPath": "/Users/user/.pyenv/versions/3.11.9/bin/python" } ``` ## ベストプラクティス ### 1. プロジェクトの想定バージョンを確認 ```bash # .python-version を確認 cat .python-version # pyproject.toml を確認 grep "requires-python" pyproject.toml ``` ### 2. 仮想環境のPythonを使用 対象プロジェクトが仮想環境を持つ場合は、そのPythonを指定: ```bash # 仮想環境の有無を確認 ls -la .venv/bin/python ls -la venv/bin/python # 絶対パスを取得 realpath .venv/bin/python ``` ### 3. バージョン互換性の確認 サーバーとターゲットのPythonバージョンを確認: ```bash # サーバー側 python --version # ターゲット側 /path/to/target/python --version ``` ## トラブルシューティング ### エラー: "Python interpreter not found" ```json { "error": { "type": "FileNotFoundError", "message": "Python interpreter not found: /path/to/python" } } ``` **解決策**: - パスが正しいか確認 - Python が実行可能か確認: `ls -l /path/to/python` - 絶対パスを使用 ### エラー: "Runner process terminated unexpectedly" **原因**: - 指定したPythonに必要なパッケージがインストールされていない - バージョンが古すぎる(< 3.11) **解決策**: ```bash # 依存関係をインストール /path/to/python -m pip install pydantic # バージョン確認 /path/to/python --version ``` ### Path オブジェクトのエラーが継続する場合 **応急処置**: 1. サーバーとターゲットを同じPythonバージョンに統一 2. 仮想環境を作り直す ```bash # 古い環境を削除 rm -rf .venv # 新しい環境を作成(同じバージョンで) python3.11 -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` ## テスト 統合テストで動作確認: ```bash # pythonPath サポートのテスト uv run pytest tests/integration/test_python_path.py -v # 全体のテスト uv run pytest tests/integration/ -v ``` ## 技術的詳細 ### selectによるタイムアウト処理 ```python import select ready, _, _ = select.select([process.stdout], [], [], timeout) if ready: response = process.stdout.readline() else: # Timeout handling ``` ### プロセス終了の確認 ```python # subprocess.Popen の場合 if process.poll() is not None: # プロセスは終了している # multiprocessing.Process の場合(旧実装) if not process.is_alive(): # プロセスは終了している ``` ### グレースフル終了 ```python # 1. 終了コマンドを送信 terminate_cmd = json.dumps({"command": "terminate"}) + '\n' process.stdin.write(terminate_cmd) process.wait(timeout=5) # 2. SIGTERM process.terminate() process.wait(timeout=5) # 3. SIGKILL(最終手段) process.kill() process.wait() ``` ## 制限事項 1. **Python 3.11 以上が必須** - サーバー自体は 3.11+ が必要 - ターゲットも 3.11+ 推奨(古いバージョンは動作保証外) 2. **クロスプラットフォーム** - `select.select()` は Windows で制限あり - Windows では将来的に `asyncio` への移行が必要 3. **依存関係** - ターゲットの Python に `pydantic` が必要 - デバッグ対象のコードが使用するパッケージも必要 ## 今後の改善 - [ ] Windows サポート(asyncio subprocess) - [ ] 自動的な仮想環境検出 - [ ] Python バージョン互換性チェック - [ ] 依存関係の自動インストール

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Kaina3/Debug-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server