UNPKG

@physictim/mcp-server-thsrc

Version:

🚄 MCP Server for Taiwan High Speed Rail - Real-time train schedules, station info, and seat availability. Cross-platform support with automatic Python detection. 台灣高鐵即時資訊查詢服務,支援 Windows/macOS/Linux,提供時刻表、班次狀態、座位查詢等功能。

448 lines (395 loc) 14.7 kB
#!/usr/bin/env node const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const os = require('os'); // 顯示幫助信息 function showHelp() { console.log(` MCP Server THSRC - 台灣高鐵資訊查詢服務 使用方式: npx @physictim/mcp-server-thsrc 環境變數: TDX_CLIENT_ID - TDX API Client ID (必要) TDX_CLIENT_SECRET - TDX API Client Secret (必要) 範例 Claude Desktop 配置: { "mcpServers": { "thsrc": { "command": "npx", "args": ["-y", "@physictim/mcp-server-thsrc"], "env": { "TDX_CLIENT_ID": "your_client_id", "TDX_CLIENT_SECRET": "your_client_secret" } } } } 更多資訊:https://github.com/physictim/thsrc_mcp `); } // 檢查環境變數 function checkEnvironment() { if (!process.env.TDX_CLIENT_ID || !process.env.TDX_CLIENT_SECRET) { console.error('錯誤:缺少必要的環境變數'); console.error(' 請設定 TDX_CLIENT_ID 和 TDX_CLIENT_SECRET'); console.error(' 前往 https://tdx.transportdata.tw/ 註冊並取得 API 金鑰'); console.error(''); showHelp(); process.exit(1); } } // 檢查 Python 是否安裝 function checkPython() { // 首先檢查是否有環境變數指定 Python 路徑 if (process.env.PYTHON_PATH) { const pythonPath = process.env.PYTHON_PATH; console.error(`使用環境變數指定的 Python: ${pythonPath}`); try { const result = require('child_process').execSync(`"${pythonPath}" --version`, { encoding: 'utf8', stdio: 'pipe', shell: true }); if (result.includes('Python 3.')) { const version = result.match(/Python (\d+)\.(\d+)/); if (version) { const major = parseInt(version[1]); const minor = parseInt(version[2]); const versionString = `${major}.${minor}`; // Python 3.8 以上版本 if (major > 3 || (major === 3 && minor >= 8)) { return `"${pythonPath}"`; } else { console.error(`Python 版本太舊: Python ${versionString} (需要 >= 3.8)`); } } else { console.error(`無法解析 Python 版本: ${result.trim()}`); } } } catch (e) { console.error(`無法使用指定的 Python 路徑: ${e.message}`); } } // 針對不同作業系統的 Python 路徑和檢測策略 const platform = os.platform(); const isWindows = platform === 'win32'; let testedPaths = []; // 記錄所有嘗試過的路徑 // 調試用:顯示偵測到的平台 if (process.env.DEBUG_PYTHON_DETECTION) { console.error(`[DEBUG] Detected platform: ${platform}, isWindows: ${isWindows}`); } if (isWindows) { // Windows 特殊處理:優先使用 py launcher const pyLaunchers = [ 'py -3', // 使用最新 Python 3 'py -3.12', // 具體版本 'py -3.11', 'py -3.10', 'py -3.9', 'py -3.8', 'py', // 默認版本 'python', // 直接命令 'python3' ]; for (const pyCmd of pyLaunchers) { try { testedPaths.push(pyCmd); const result = require('child_process').execSync(`${pyCmd} --version`, { encoding: 'utf8', stdio: 'pipe', shell: true }); if (result.includes('Python 3.')) { const version = result.match(/Python (\d+)\.(\d+)/); if (version) { const major = parseInt(version[1]); const minor = parseInt(version[2]); if (major > 3 || (major === 3 && minor >= 8)) { return pyCmd; } } } } catch (e) { // 繼續嘗試下一個 } } // 如果 py launcher 失敗,嘗試直接路徑 const directPaths = [ 'C:\\Python313\\python.exe', // 優先檢查 Python 3.13 'C:\\Python312\\python.exe', 'C:\\Python311\\python.exe', 'C:\\Python310\\python.exe', 'C:\\Python39\\python.exe', 'C:\\Python38\\python.exe', 'C:\\Python3\\python.exe', 'C:\\Python\\python.exe' ]; // 也嘗試用戶目錄 const username = os.userInfo().username; const userPaths = [ `C:\\Users\\${username}\\AppData\\Local\\Programs\\Python\\Python313\\python.exe`, `C:\\Users\\${username}\\AppData\\Local\\Programs\\Python\\Python312\\python.exe`, `C:\\Users\\${username}\\AppData\\Local\\Programs\\Python\\Python311\\python.exe`, `C:\\Users\\${username}\\AppData\\Local\\Programs\\Python\\Python310\\python.exe`, `C:\\Users\\${username}\\AppData\\Local\\Programs\\Python\\Python39\\python.exe`, `C:\\Users\\${username}\\AppData\\Local\\Programs\\Python\\Python38\\python.exe`, // Anaconda Python `C:\\Users\\${username}\\anaconda3\\python.exe`, `C:\\Users\\${username}\\Anaconda3\\python.exe`, `C:\\Users\\${username}\\miniconda3\\python.exe`, `C:\\Users\\${username}\\Miniconda3\\python.exe` ]; // 檢查 Program Files 目錄 const programPaths = [ 'C:\\Program Files\\Python313\\python.exe', 'C:\\Program Files\\Python312\\python.exe', 'C:\\Program Files\\Python311\\python.exe', 'C:\\Program Files\\Python310\\python.exe', 'C:\\Program Files\\Python39\\python.exe', 'C:\\Program Files\\Python38\\python.exe', 'C:\\Program Files (x86)\\Python313\\python.exe', 'C:\\Program Files (x86)\\Python312\\python.exe', 'C:\\Program Files (x86)\\Python311\\python.exe', 'C:\\Program Files (x86)\\Python310\\python.exe', 'C:\\Program Files (x86)\\Python39\\python.exe', 'C:\\Program Files (x86)\\Python38\\python.exe' ]; for (const pythonPath of [...directPaths, ...userPaths, ...programPaths]) { if (fs.existsSync(pythonPath)) { try { testedPaths.push(pythonPath); // 在 MCP 模式下使用 stderr 輸出調試訊息 if (process.env.DEBUG_PYTHON_DETECTION) { console.error(`[DEBUG] Testing Python at: ${pythonPath}`); } const result = require('child_process').execSync(`"${pythonPath}" --version`, { encoding: 'utf8', stdio: 'pipe', shell: true, windowsHide: true // 避免 Windows 上閃現命令窗口 }); if (process.env.DEBUG_PYTHON_DETECTION) { console.error(`[DEBUG] Python version output: ${result.trim()}`); } if (result.includes('Python 3.')) { const version = result.match(/Python (\d+)\.(\d+)/); if (version) { const major = parseInt(version[1]); const minor = parseInt(version[2]); if (major > 3 || (major === 3 && minor >= 8)) { if (process.env.DEBUG_PYTHON_DETECTION) { console.error(`[DEBUG] Found valid Python ${major}.${minor} at: ${pythonPath}`); } return `"${pythonPath}"`; } } } } catch (e) { if (process.env.DEBUG_PYTHON_DETECTION) { console.error(`[DEBUG] Failed to test Python at ${pythonPath}: ${e.message}`); } // 繼續嘗試下一個 } } } } else { // macOS/Linux 的檢測邏輯 const pythonPaths = [ 'python3', 'python', '/usr/local/bin/python3', '/usr/local/bin/python', '/usr/bin/python3', '/usr/bin/python', '/opt/homebrew/bin/python3', '/opt/homebrew/bin/python' ]; for (const pythonCmd of pythonPaths) { try { testedPaths.push(pythonCmd); const result = require('child_process').execSync(`${pythonCmd} --version`, { encoding: 'utf8', stdio: 'pipe' }); if (result.includes('Python 3.')) { const version = result.match(/Python (\d+)\.(\d+)/); if (version) { const major = parseInt(version[1]); const minor = parseInt(version[2]); if (major > 3 || (major === 3 && minor >= 8)) { return pythonCmd; } } } } catch (e) { // 繼續嘗試下一個 } } } console.error('錯誤:找不到 Python 3.8+ 版本'); console.error(' 請安裝 Python 3.8 或更新版本:'); if (isWindows) { console.error(' - Windows: 從 https://python.org 下載安裝'); console.error(' 或使用 Microsoft Store 安裝 Python'); console.error(' 或使用 winget: winget install Python.Python.3.12'); } else { console.error(' - macOS: brew install python3'); console.error(' - Ubuntu: sudo apt install python3 python3-pip'); } console.error(''); console.error(' 已嘗試的路徑:'); testedPaths.forEach(path => console.error(` - ${path}`)); process.exit(1); } // 安裝 Python 依賴 function installDependencies(pythonCmd) { const requirementsPath = path.join(__dirname, 'requirements.txt'); log('檢查 Python 依賴...'); // 檢查是否已安裝依賴 try { require('child_process').execSync( `${pythonCmd} -c "import httpx, fastmcp, dotenv"`, { stdio: 'pipe' } ); log('Python 依賴已安裝'); return; // 依賴已安裝 } catch (e) { // 需要安裝依賴 } log('首次執行,正在安裝 Python 依賴...'); log('這可能需要幾分鐘時間...'); try { // 在 MCP 模式下使用安靜模式安裝,避免輸出干擾 const quietFlag = isMCPMode() ? '--quiet' : ''; const stdio = isMCPMode() ? 'pipe' : 'inherit'; const isWindows = os.platform() === 'win32'; // Windows 和 Unix 有不同的 pip 安裝策略 const installStrategies = isWindows ? [ // Windows 策略 `${pythonCmd} -m pip install ${quietFlag} -r "${requirementsPath}"`, // 不使用 --user,避免權限問題 `${pythonCmd} -m pip install --user ${quietFlag} -r "${requirementsPath}"`, // 回退到 --user `py -m pip install ${quietFlag} -r "${requirementsPath}"`, // 使用 py launcher `py -m pip install --user ${quietFlag} -r "${requirementsPath}"` ] : [ // Unix 策略 `${pythonCmd} -m pip install --user ${quietFlag} -r "${requirementsPath}"`, // 正常安裝 `${pythonCmd} -m pip install --user --break-system-packages ${quietFlag} -r "${requirementsPath}"` // Python 3.12+ 回退 ]; let installSuccess = false; let lastError = null; for (const strategy of installStrategies) { try { require('child_process').execSync(strategy, { stdio, shell: isWindows // Windows 需要 shell }); installSuccess = true; break; } catch (err) { lastError = err; // 繼續嘗試下一個策略 } } if (!installSuccess) { throw lastError; } log('依賴安裝完成!'); } catch (e) { const isWindows = os.platform() === 'win32'; console.error('安裝依賴失敗'); if (isWindows) { console.error(' Windows 用戶建議:'); console.error(' 1. 確保以管理員身份運行 Command Prompt 或 PowerShell'); console.error(' 2. 或使用以下命令手動安裝:'); console.error(' py -m pip install httpx fastmcp python-dotenv'); console.error(' 3. 或從 Microsoft Store 安裝 Python 並重試'); } else { console.error(' 建議使用 pipx 安裝(推薦):'); console.error(' pipx install git+https://github.com/physictim/thsrc_mcp.git'); console.error(''); console.error(' 或手動執行:'); console.error(` ${pythonCmd} -m pip install --user --break-system-packages httpx fastmcp python-dotenv`); } console.error(''); console.error(' 或使用 pipx 安裝:'); console.error(' pipx install git+https://github.com/physictim/thsrc_mcp.git'); process.exit(1); } } // 檢測是否在 MCP 環境中運行 function isMCPMode() { // 如果 stdout 是 pipe,通常表示在 MCP 環境中 return !process.stdout.isTTY; } // 安全的日誌輸出(只在非 MCP 模式下輸出) function log(message) { if (!isMCPMode()) { console.log(message); } } // 主函數 function main() { // 處理命令行參數 const args = process.argv.slice(2); if (args.includes('--help') || args.includes('-h')) { showHelp(); process.exit(0); } log('啟動 MCP Server THSRC...'); // 檢查環境 checkEnvironment(); const pythonCmd = checkPython(); const scriptPath = path.join(__dirname, 'thsrc.py'); // 檢查腳本文件是否存在 if (!fs.existsSync(scriptPath)) { console.error('錯誤:找不到 thsrc.py 文件'); console.error(' 套件可能安裝不完整,請重新安裝:'); console.error(' npm uninstall -g @physictim/mcp-server-thsrc'); console.error(' npx @physictim/mcp-server-thsrc'); process.exit(1); } // 首次執行時安裝依賴 installDependencies(pythonCmd); log('連接 MCP 協議...'); // 執行 Python 腳本 // Windows 需要特殊處理帶引號的路徑 const isWindows = os.platform() === 'win32'; let spawnCmd, spawnArgs; if (isWindows && pythonCmd.includes('"')) { // Windows 上如果 pythonCmd 包含引號(表示是完整路徑),需要特殊處理 spawnCmd = pythonCmd.replace(/"/g, ''); // 移除引號 spawnArgs = [scriptPath]; } else if (isWindows && pythonCmd.startsWith('py')) { // 使用 py launcher 時,需要通過 shell 執行 spawnCmd = 'cmd'; spawnArgs = ['/c', `${pythonCmd} "${scriptPath}"`]; } else { // Unix 系統或 Windows 上的簡單命令 spawnCmd = pythonCmd; spawnArgs = [scriptPath]; } const child = spawn(spawnCmd, spawnArgs, { stdio: 'inherit', env: process.env, shell: isWindows && pythonCmd.startsWith('py') // py launcher 需要 shell }); child.on('error', (err) => { console.error('執行錯誤:', err.message); process.exit(1); }); child.on('exit', (code) => { if (code !== 0) { console.error(`程序異常退出,代碼: ${code}`); } process.exit(code); }); // 處理中斷信號 process.on('SIGINT', () => { log('\n正在關閉 MCP Server THSRC...'); child.kill('SIGINT'); }); process.on('SIGTERM', () => { log('\n正在關閉 MCP Server THSRC...'); child.kill('SIGTERM'); }); } if (require.main === module) { main(); }