pyodide_install-packages
Install Python packages in Pyodide environments using space-separated format, enabling Python code execution via LLMs on the MCP server.
Instructions
Install Python packages using Pyodide. Multiple packages can be specified using space-separated format.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| package | Yes | Python package(s) to install. For multiple packages, use space-separated format (e.g., 'numpy matplotlib pandas'). |
Implementation Reference
- Primary handler function executing the pyodide_install-packages tool logic: installs one or more Python packages via Pyodide, with sophisticated fallback mechanism for unsupported packages.async installPackage(packageName: string) { if (!this.pyodide) { return formatCallToolError("Pyodide not initialized"); } try { // パッケージ名をスペースで分割 const packages = packageName .split(" ") .map((pkg) => pkg.trim()) .filter(Boolean); if (packages.length === 0) { return formatCallToolError("No valid package names specified"); } // 出力メッセージを集める const outputs: string[] = []; // 各パッケージを処理 for (const pkg of packages) { try { // 1. まずpyodide.loadPackageでインストールを試みる outputs.push(`Attempting to install ${pkg} using loadPackage...`); try { await this.pyodide.loadPackage(pkg, { messageCallback: (msg) => { outputs.push(`loadPackage: ${msg}`); }, errorCallback: (err) => { throw new Error(err); }, }); outputs.push(`Successfully installed ${pkg} using loadPackage.`); continue; // このパッケージは成功したので次のパッケージへ } catch (loadPackageError) { outputs.push( `loadPackage failed for ${pkg}: ${ loadPackageError instanceof Error ? loadPackageError.message : String(loadPackageError) }` ); outputs.push(`Falling back to micropip for ${pkg}...`); // loadPackageが失敗した場合は、micropipを使用する // micropipがまだロードされていない場合はロードする try { // micropipをロードする await this.pyodide.loadPackage("micropip", { messageCallback: (msg) => { outputs.push(`loadPackage: ${msg}`); }, errorCallback: (err) => { throw new Error(err); }, }); } catch (micropipLoadError) { throw new Error( `Failed to load micropip: ${ micropipLoadError instanceof Error ? micropipLoadError.message : String(micropipLoadError) }` ); } // 2. micropipを使ったインストール処理 // 一時ディレクトリを作成 const tempDir = process.env.PYODIDE_CACHE_DIR || "./cache"; if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } // Pyodide内のtempディレクトリを作成 this.pyodide.FS.mkdirTree("/tmp/wheels"); // PyPIからwheelのURLを取得 const wheelUrl = await getWheelUrl(pkg); const wheelFilename = path.basename(wheelUrl); const localWheelPath = path.join(tempDir, wheelFilename); // wheelをダウンロード outputs.push(`Downloading wheel for ${pkg}...`); await downloadWheel(wheelUrl, localWheelPath); // wheelをPyodideのファイルシステムにコピー const wheelData = fs.readFileSync(localWheelPath); const pyodideWheelPath = `/tmp/wheels/${wheelFilename}`; this.pyodide.FS.writeFile(pyodideWheelPath, wheelData); // micropipでインストール const { output } = await withOutputCapture( this.pyodide, async () => { await this.pyodide!.runPythonAsync(` import micropip await micropip.install("emfs:${pyodideWheelPath}") `); }, { suppressConsole: true } ); outputs.push( `Successfully installed ${pkg} using micropip: ${output}` ); } } catch (error) { // 個別のパッケージのエラーを記録して続行 outputs.push( `Failed to install ${pkg}: ${ error instanceof Error ? error.message : String(error) }` ); } } return formatCallToolSuccess(outputs.join("\n\n")); } catch (error) { return formatCallToolError(error); } }
- src/tools/index.ts:23-38 (schema)MCP tool schema definition for pyodide_install-packages, including input schema for the 'package' parameter.export const INSTALL_PYTHON_PACKAGES_TOOL: Tool = { name: "pyodide_install-packages", description: "Install Python packages using Pyodide. Multiple packages can be specified using space-separated format.", inputSchema: { type: "object", properties: { package: { type: "string", description: "Python package(s) to install. For multiple packages, use space-separated format (e.g., 'numpy matplotlib pandas').", }, }, required: ["package"], }, };
- src/handlers/index.ts:32-38 (registration)Tool registration: includes INSTALL_PYTHON_PACKAGES_TOOL in the list returned by ListToolsRequestHandler.const TOOLS: Tool[] = [ tools.EXECUTE_PYTHON_TOOL, tools.INSTALL_PYTHON_PACKAGES_TOOL, tools.GET_MOUNT_POINTS_TOOL, tools.LIST_MOUNTED_DIRECTORY_TOOL, tools.READ_IMAGE_TOOL, ];
- src/handlers/index.ts:96-104 (handler)MCP server handler dispatcher for the tool: argument validation and invocation of PyodideManager.installPackage.case "pyodide_install-packages": { const installPythonPackagesArgs = isInstallPythonPackagesArgs(args); if (installPythonPackagesArgs instanceof type.errors) { throw installPythonPackagesArgs; } const { package: packageName } = installPythonPackagesArgs; const results = await pyodideManager.installPackage(packageName); return results; }
- Supporting function to obtain PyPI wheel URL used in manual installation fallback.async function getWheelUrl(packageName: string): Promise<string> { return new Promise((resolve, reject) => { const url = `https://pypi.org/pypi/${packageName}/json`; https .get(url, (response) => { if (response.statusCode !== 200) { reject( new Error(`Failed to get package info: ${response.statusCode}`) ); return; } let data = ""; response.on("data", (chunk) => { data += chunk; }); response.on("end", () => { try { const packageInfo = JSON.parse(data); const releases = packageInfo.releases[packageInfo.info.version]; // py3-none-any.whl形式のWheelファイルを検索 const wheel = releases.find( (release: any) => release.packagetype === "bdist_wheel" && release.filename.includes("py3-none-any.whl") ); if (wheel) { resolve(wheel.url); } else { reject(new Error(`No compatible wheel found for ${packageName}`)); } } catch (error) { reject(error); } }); }) .on("error", reject); }); }