diff --git a/Dockerfile b/Dockerfile index 2068dd3..fb47dec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,7 +34,6 @@ RUN if [ "$INSTALL_EXT" = "true" ]; then \ fi RUN uv tool install mcp-server-fetch -ENV UV_PYTHON_INSTALL_MIRROR="http://mirrors.aliyun.com/pypi/simple/" WORKDIR /app diff --git a/entrypoint.sh b/entrypoint.sh index 999f05b..03f3d71 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -18,6 +18,5 @@ if [ -n "$HTTPS_PROXY" ]; then fi echo "Using REQUEST_TIMEOUT: $REQUEST_TIMEOUT" -echo "Using UV_PYTHON_INSTALL_MIRROR: $UV_PYTHON_INSTALL_MIRROR" exec "$@" diff --git a/frontend/src/hooks/useSettingsData.ts b/frontend/src/hooks/useSettingsData.ts index dd6dce3..270e9ff 100644 --- a/frontend/src/hooks/useSettingsData.ts +++ b/frontend/src/hooks/useSettingsData.ts @@ -9,9 +9,14 @@ interface RoutingConfig { enableGroupNameRoute: boolean; } +interface InstallConfig { + pythonIndexUrl: string; +} + interface SystemSettings { systemConfig?: { routing?: RoutingConfig; + install?: InstallConfig; }; } @@ -24,6 +29,10 @@ export const useSettingsData = () => { enableGroupNameRoute: true, }); + const [installConfig, setInstallConfig] = useState({ + pythonIndexUrl: '', + }); + const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [refreshKey, setRefreshKey] = useState(0); @@ -58,6 +67,12 @@ export const useSettingsData = () => { enableGroupNameRoute: data.data.systemConfig.routing.enableGroupNameRoute ?? true, }); } + + if (data.success && data.data?.systemConfig?.install) { + setInstallConfig({ + pythonIndexUrl: data.data.systemConfig.install.pythonIndexUrl || '', + }); + } } catch (error) { console.error('Failed to fetch settings:', error); setError(error instanceof Error ? error.message : 'Failed to fetch settings'); @@ -114,6 +129,53 @@ export const useSettingsData = () => { } }; + // Update install configuration + const updateInstallConfig = async (key: keyof InstallConfig, value: string) => { + setLoading(true); + setError(null); + + try { + const token = localStorage.getItem('mcphub_token'); + const response = await fetch('/api/system-config', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'x-auth-token': token || '', + }, + body: JSON.stringify({ + install: { + [key]: value, + }, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + + if (data.success) { + setInstallConfig({ + ...installConfig, + [key]: value, + }); + showToast(t('settings.systemConfigUpdated')); + return true; + } else { + showToast(t('errors.failedToUpdateSystemConfig')); + return false; + } + } catch (error) { + console.error('Failed to update system config:', error); + setError(error instanceof Error ? error.message : 'Failed to update system config'); + showToast(t('errors.failedToUpdateSystemConfig')); + return false; + } finally { + setLoading(false); + } + }; + // Fetch settings when the component mounts or refreshKey changes useEffect(() => { fetchSettings(); @@ -121,11 +183,13 @@ export const useSettingsData = () => { return { routingConfig, + installConfig, loading, error, setError, triggerRefresh, fetchSettings, updateRoutingConfig, + updateInstallConfig, }; }; diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index a8355b3..1ba17e2 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -148,7 +148,8 @@ "account": "Account Settings", "password": "Change Password", "appearance": "Appearance", - "routeConfig": "Route Configuration" + "routeConfig": "Route Configuration", + "installConfig": "Installation Configuration" }, "market": { "title": "Server Market - (Data from mcpm.sh)" @@ -243,6 +244,10 @@ "enableGlobalRouteDescription": "Allow connections to /sse endpoint without specifying a group ID", "enableGroupNameRoute": "Enable Group Name Route", "enableGroupNameRouteDescription": "Allow connections to /sse endpoint using group names instead of just group IDs", + "pythonIndexUrl": "Python Package Repository URL", + "pythonIndexUrlDescription": "Set UV_DEFAULT_INDEX environment variable for Python package installation", + "pythonIndexUrlPlaceholder": "e.g. https://pypi.org/simple", + "installConfig": "Installation Configuration", "systemConfigUpdated": "System configuration updated successfully" } } \ No newline at end of file diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index 67d14fc..37a7d50 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -146,7 +146,8 @@ "account": "账户设置", "password": "修改密码", "appearance": "外观", - "routeConfig": "路由配置" + "routeConfig": "路由配置", + "installConfig": "安装配置" }, "groups": { "title": "分组管理" @@ -241,9 +242,13 @@ }, "settings": { "enableGlobalRoute": "启用全局路由", - "enableGlobalRouteDescription": "允许不指定分组 ID 就连接到 /sse 端点", - "enableGroupNameRoute": "启用分组名称路由", - "enableGroupNameRouteDescription": "允许使用分组名称而非分组 ID 连接到 /sse 端点", + "enableGlobalRouteDescription": "允许不指定组 ID 就连接到 /sse 端点", + "enableGroupNameRoute": "启用组名路由", + "enableGroupNameRouteDescription": "允许使用组名而不仅仅是组 ID 连接到 /sse 端点", + "pythonIndexUrl": "Python 包仓库地址", + "pythonIndexUrlDescription": "设置 UV_DEFAULT_INDEX 环境变量,用于 Python 包安装", + "pythonIndexUrlPlaceholder": "例如: https://mirrors.aliyun.com/pypi/simple", + "installConfig": "安装配置", "systemConfigUpdated": "系统配置更新成功" } } \ No newline at end of file diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 31d341a..cf16c2c 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -17,18 +17,34 @@ const SettingsPage: React.FC = () => { setCurrentLanguage(i18n.language); }, [i18n.language]); + const [installConfig, setInstallConfig] = useState<{ + pythonIndexUrl: string; + }>({ + pythonIndexUrl: '', + }); + const { routingConfig, + installConfig: savedInstallConfig, loading, - updateRoutingConfig + updateRoutingConfig, + updateInstallConfig } = useSettingsData(); + // Update local installConfig when savedInstallConfig changes + useEffect(() => { + if (savedInstallConfig) { + setInstallConfig(savedInstallConfig); + } + }, [savedInstallConfig]); + const [sectionsVisible, setSectionsVisible] = useState({ routingConfig: false, + installConfig: false, password: false }); - const toggleSection = (section: 'routingConfig' | 'password') => { + const toggleSection = (section: 'routingConfig' | 'installConfig' | 'password') => { setSectionsVisible(prev => ({ ...prev, [section]: !prev[section] @@ -39,6 +55,17 @@ const SettingsPage: React.FC = () => { await updateRoutingConfig(key, value); }; + const handleInstallConfigChange = (value: string) => { + setInstallConfig({ + ...installConfig, + pythonIndexUrl: value + }); + }; + + const saveInstallConfig = async () => { + await updateInstallConfig('pythonIndexUrl', installConfig.pythonIndexUrl); + }; + const handlePasswordChangeSuccess = () => { setTimeout(() => { navigate('/'); @@ -124,6 +151,47 @@ const SettingsPage: React.FC = () => { )} + {/* Installation Configuration Settings */} +
+
toggleSection('installConfig')} + > +

{t('settings.installConfig')}

+ + {sectionsVisible.installConfig ? '▼' : '►'} + +
+ + {sectionsVisible.installConfig && ( +
+
+
+

{t('settings.pythonIndexUrl')}

+

{t('settings.pythonIndexUrlDescription')}

+
+
+ handleInstallConfigChange(e.target.value)} + placeholder={t('settings.pythonIndexUrlPlaceholder')} + className="flex-1 mt-1 block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + disabled={loading} + /> + +
+
+
+ )} +
+ {/* Change Password */}
= export const updateSystemConfig = (req: Request, res: Response): void => { try { - const { routing } = req.body; + const { routing, install } = req.body; - if (!routing || (typeof routing.enableGlobalRoute !== 'boolean' && typeof routing.enableGroupNameRoute !== 'boolean')) { + if ((!routing || (typeof routing.enableGlobalRoute !== 'boolean' && typeof routing.enableGroupNameRoute !== 'boolean')) + && (!install || typeof install.pythonIndexUrl !== 'string')) { res.status(400).json({ success: false, message: 'Invalid system configuration provided', @@ -263,6 +264,9 @@ export const updateSystemConfig = (req: Request, res: Response): void => { routing: { enableGlobalRoute: true, enableGroupNameRoute: true + }, + install: { + pythonIndexUrl: '' } }; } @@ -274,12 +278,26 @@ export const updateSystemConfig = (req: Request, res: Response): void => { }; } - if (typeof routing.enableGlobalRoute === 'boolean') { - settings.systemConfig.routing.enableGlobalRoute = routing.enableGlobalRoute; + if (!settings.systemConfig.install) { + settings.systemConfig.install = { + pythonIndexUrl: '' + }; } - if (typeof routing.enableGroupNameRoute === 'boolean') { - settings.systemConfig.routing.enableGroupNameRoute = routing.enableGroupNameRoute; + if (routing) { + if (typeof routing.enableGlobalRoute === 'boolean') { + settings.systemConfig.routing.enableGlobalRoute = routing.enableGlobalRoute; + } + + if (typeof routing.enableGroupNameRoute === 'boolean') { + settings.systemConfig.routing.enableGroupNameRoute = routing.enableGroupNameRoute; + } + } + + if (install) { + if (typeof install.pythonIndexUrl === 'string') { + settings.systemConfig.install.pythonIndexUrl = install.pythonIndexUrl; + } } if (saveSettings(settings)) { diff --git a/src/services/mcpService.ts b/src/services/mcpService.ts index 695293b..811cd62 100644 --- a/src/services/mcpService.ts +++ b/src/services/mcpService.ts @@ -79,6 +79,13 @@ export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] => } else if (conf.command && conf.args) { const env: Record = conf.env || {}; env['PATH'] = expandEnvVars(process.env.PATH as string) || ''; + + // Add UV_DEFAULT_INDEX from settings if available (for Python packages) + const settings = loadSettings(); + if (settings.systemConfig?.install?.pythonIndexUrl && conf.command === 'uvx') { + env['UV_DEFAULT_INDEX'] = settings.systemConfig.install.pythonIndexUrl; + } + transport = new StdioClientTransport({ command: conf.command, args: conf.args, diff --git a/src/types/index.ts b/src/types/index.ts index a99dcf6..564d7cf 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -83,6 +83,9 @@ export interface McpSettings { enableGlobalRoute?: boolean; // Controls whether the /sse endpoint without group is enabled enableGroupNameRoute?: boolean; // Controls whether group routing by name is allowed }; + install?: { + pythonIndexUrl?: string; // Python package repository URL (UV_DEFAULT_INDEX) + }; // Add other system configuration sections here in the future }; }