mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-24 02:39:19 -05:00
feat: add installation configuration support with pythonIndexUrl in settings (#67)
This commit is contained in:
@@ -34,7 +34,6 @@ RUN if [ "$INSTALL_EXT" = "true" ]; then \
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
RUN uv tool install mcp-server-fetch
|
RUN uv tool install mcp-server-fetch
|
||||||
ENV UV_PYTHON_INSTALL_MIRROR="http://mirrors.aliyun.com/pypi/simple/"
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,5 @@ if [ -n "$HTTPS_PROXY" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Using REQUEST_TIMEOUT: $REQUEST_TIMEOUT"
|
echo "Using REQUEST_TIMEOUT: $REQUEST_TIMEOUT"
|
||||||
echo "Using UV_PYTHON_INSTALL_MIRROR: $UV_PYTHON_INSTALL_MIRROR"
|
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@@ -9,9 +9,14 @@ interface RoutingConfig {
|
|||||||
enableGroupNameRoute: boolean;
|
enableGroupNameRoute: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface InstallConfig {
|
||||||
|
pythonIndexUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface SystemSettings {
|
interface SystemSettings {
|
||||||
systemConfig?: {
|
systemConfig?: {
|
||||||
routing?: RoutingConfig;
|
routing?: RoutingConfig;
|
||||||
|
install?: InstallConfig;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +29,10 @@ export const useSettingsData = () => {
|
|||||||
enableGroupNameRoute: true,
|
enableGroupNameRoute: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [installConfig, setInstallConfig] = useState<InstallConfig>({
|
||||||
|
pythonIndexUrl: '',
|
||||||
|
});
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
@@ -58,6 +67,12 @@ export const useSettingsData = () => {
|
|||||||
enableGroupNameRoute: data.data.systemConfig.routing.enableGroupNameRoute ?? true,
|
enableGroupNameRoute: data.data.systemConfig.routing.enableGroupNameRoute ?? true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.success && data.data?.systemConfig?.install) {
|
||||||
|
setInstallConfig({
|
||||||
|
pythonIndexUrl: data.data.systemConfig.install.pythonIndexUrl || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch settings:', error);
|
console.error('Failed to fetch settings:', error);
|
||||||
setError(error instanceof Error ? error.message : 'Failed to fetch settings');
|
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
|
// Fetch settings when the component mounts or refreshKey changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSettings();
|
fetchSettings();
|
||||||
@@ -121,11 +183,13 @@ export const useSettingsData = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
routingConfig,
|
routingConfig,
|
||||||
|
installConfig,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
setError,
|
setError,
|
||||||
triggerRefresh,
|
triggerRefresh,
|
||||||
fetchSettings,
|
fetchSettings,
|
||||||
updateRoutingConfig,
|
updateRoutingConfig,
|
||||||
|
updateInstallConfig,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -148,7 +148,8 @@
|
|||||||
"account": "Account Settings",
|
"account": "Account Settings",
|
||||||
"password": "Change Password",
|
"password": "Change Password",
|
||||||
"appearance": "Appearance",
|
"appearance": "Appearance",
|
||||||
"routeConfig": "Route Configuration"
|
"routeConfig": "Route Configuration",
|
||||||
|
"installConfig": "Installation Configuration"
|
||||||
},
|
},
|
||||||
"market": {
|
"market": {
|
||||||
"title": "Server Market - (Data from mcpm.sh)"
|
"title": "Server Market - (Data from mcpm.sh)"
|
||||||
@@ -243,6 +244,10 @@
|
|||||||
"enableGlobalRouteDescription": "Allow connections to /sse endpoint without specifying a group ID",
|
"enableGlobalRouteDescription": "Allow connections to /sse endpoint without specifying a group ID",
|
||||||
"enableGroupNameRoute": "Enable Group Name Route",
|
"enableGroupNameRoute": "Enable Group Name Route",
|
||||||
"enableGroupNameRouteDescription": "Allow connections to /sse endpoint using group names instead of just group IDs",
|
"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"
|
"systemConfigUpdated": "System configuration updated successfully"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,8 @@
|
|||||||
"account": "账户设置",
|
"account": "账户设置",
|
||||||
"password": "修改密码",
|
"password": "修改密码",
|
||||||
"appearance": "外观",
|
"appearance": "外观",
|
||||||
"routeConfig": "路由配置"
|
"routeConfig": "路由配置",
|
||||||
|
"installConfig": "安装配置"
|
||||||
},
|
},
|
||||||
"groups": {
|
"groups": {
|
||||||
"title": "分组管理"
|
"title": "分组管理"
|
||||||
@@ -241,9 +242,13 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"enableGlobalRoute": "启用全局路由",
|
"enableGlobalRoute": "启用全局路由",
|
||||||
"enableGlobalRouteDescription": "允许不指定分组 ID 就连接到 /sse 端点",
|
"enableGlobalRouteDescription": "允许不指定组 ID 就连接到 /sse 端点",
|
||||||
"enableGroupNameRoute": "启用分组名称路由",
|
"enableGroupNameRoute": "启用组名路由",
|
||||||
"enableGroupNameRouteDescription": "允许使用分组名称而非分组 ID 连接到 /sse 端点",
|
"enableGroupNameRouteDescription": "允许使用组名而不仅仅是组 ID 连接到 /sse 端点",
|
||||||
|
"pythonIndexUrl": "Python 包仓库地址",
|
||||||
|
"pythonIndexUrlDescription": "设置 UV_DEFAULT_INDEX 环境变量,用于 Python 包安装",
|
||||||
|
"pythonIndexUrlPlaceholder": "例如: https://mirrors.aliyun.com/pypi/simple",
|
||||||
|
"installConfig": "安装配置",
|
||||||
"systemConfigUpdated": "系统配置更新成功"
|
"systemConfigUpdated": "系统配置更新成功"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,18 +17,34 @@ const SettingsPage: React.FC = () => {
|
|||||||
setCurrentLanguage(i18n.language);
|
setCurrentLanguage(i18n.language);
|
||||||
}, [i18n.language]);
|
}, [i18n.language]);
|
||||||
|
|
||||||
|
const [installConfig, setInstallConfig] = useState<{
|
||||||
|
pythonIndexUrl: string;
|
||||||
|
}>({
|
||||||
|
pythonIndexUrl: '',
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routingConfig,
|
routingConfig,
|
||||||
|
installConfig: savedInstallConfig,
|
||||||
loading,
|
loading,
|
||||||
updateRoutingConfig
|
updateRoutingConfig,
|
||||||
|
updateInstallConfig
|
||||||
} = useSettingsData();
|
} = useSettingsData();
|
||||||
|
|
||||||
|
// Update local installConfig when savedInstallConfig changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (savedInstallConfig) {
|
||||||
|
setInstallConfig(savedInstallConfig);
|
||||||
|
}
|
||||||
|
}, [savedInstallConfig]);
|
||||||
|
|
||||||
const [sectionsVisible, setSectionsVisible] = useState({
|
const [sectionsVisible, setSectionsVisible] = useState({
|
||||||
routingConfig: false,
|
routingConfig: false,
|
||||||
|
installConfig: false,
|
||||||
password: false
|
password: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleSection = (section: 'routingConfig' | 'password') => {
|
const toggleSection = (section: 'routingConfig' | 'installConfig' | 'password') => {
|
||||||
setSectionsVisible(prev => ({
|
setSectionsVisible(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[section]: !prev[section]
|
[section]: !prev[section]
|
||||||
@@ -39,6 +55,17 @@ const SettingsPage: React.FC = () => {
|
|||||||
await updateRoutingConfig(key, value);
|
await updateRoutingConfig(key, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleInstallConfigChange = (value: string) => {
|
||||||
|
setInstallConfig({
|
||||||
|
...installConfig,
|
||||||
|
pythonIndexUrl: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveInstallConfig = async () => {
|
||||||
|
await updateInstallConfig('pythonIndexUrl', installConfig.pythonIndexUrl);
|
||||||
|
};
|
||||||
|
|
||||||
const handlePasswordChangeSuccess = () => {
|
const handlePasswordChangeSuccess = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
@@ -124,6 +151,47 @@ const SettingsPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Installation Configuration Settings */}
|
||||||
|
<div className="bg-white shadow rounded-lg py-4 px-6 mb-6">
|
||||||
|
<div
|
||||||
|
className="flex justify-between items-center cursor-pointer"
|
||||||
|
onClick={() => toggleSection('installConfig')}
|
||||||
|
>
|
||||||
|
<h2 className="font-semibold text-gray-800">{t('settings.installConfig')}</h2>
|
||||||
|
<span className="text-gray-500">
|
||||||
|
{sectionsVisible.installConfig ? '▼' : '►'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{sectionsVisible.installConfig && (
|
||||||
|
<div className="space-y-4 mt-4">
|
||||||
|
<div className="p-3 bg-gray-50 rounded-md">
|
||||||
|
<div className="mb-2">
|
||||||
|
<h3 className="font-medium text-gray-700">{t('settings.pythonIndexUrl')}</h3>
|
||||||
|
<p className="text-sm text-gray-500">{t('settings.pythonIndexUrlDescription')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={installConfig.pythonIndexUrl}
|
||||||
|
onChange={(e) => 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}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={saveInstallConfig}
|
||||||
|
disabled={loading}
|
||||||
|
className="mt-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md text-sm font-medium disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{t('common.save')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Change Password */}
|
{/* Change Password */}
|
||||||
<div className="bg-white shadow rounded-lg py-4 px-6 mb-6">
|
<div className="bg-white shadow rounded-lg py-4 px-6 mb-6">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -247,9 +247,10 @@ export const toggleServer = async (req: Request, res: Response): Promise<void> =
|
|||||||
|
|
||||||
export const updateSystemConfig = (req: Request, res: Response): void => {
|
export const updateSystemConfig = (req: Request, res: Response): void => {
|
||||||
try {
|
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({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Invalid system configuration provided',
|
message: 'Invalid system configuration provided',
|
||||||
@@ -263,6 +264,9 @@ export const updateSystemConfig = (req: Request, res: Response): void => {
|
|||||||
routing: {
|
routing: {
|
||||||
enableGlobalRoute: true,
|
enableGlobalRoute: true,
|
||||||
enableGroupNameRoute: true
|
enableGroupNameRoute: true
|
||||||
|
},
|
||||||
|
install: {
|
||||||
|
pythonIndexUrl: ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -274,12 +278,26 @@ export const updateSystemConfig = (req: Request, res: Response): void => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof routing.enableGlobalRoute === 'boolean') {
|
if (!settings.systemConfig.install) {
|
||||||
settings.systemConfig.routing.enableGlobalRoute = routing.enableGlobalRoute;
|
settings.systemConfig.install = {
|
||||||
|
pythonIndexUrl: ''
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof routing.enableGroupNameRoute === 'boolean') {
|
if (routing) {
|
||||||
settings.systemConfig.routing.enableGroupNameRoute = routing.enableGroupNameRoute;
|
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)) {
|
if (saveSettings(settings)) {
|
||||||
|
|||||||
@@ -79,6 +79,13 @@ export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] =>
|
|||||||
} else if (conf.command && conf.args) {
|
} else if (conf.command && conf.args) {
|
||||||
const env: Record<string, string> = conf.env || {};
|
const env: Record<string, string> = conf.env || {};
|
||||||
env['PATH'] = expandEnvVars(process.env.PATH as string) || '';
|
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({
|
transport = new StdioClientTransport({
|
||||||
command: conf.command,
|
command: conf.command,
|
||||||
args: conf.args,
|
args: conf.args,
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ export interface McpSettings {
|
|||||||
enableGlobalRoute?: boolean; // Controls whether the /sse endpoint without group is enabled
|
enableGlobalRoute?: boolean; // Controls whether the /sse endpoint without group is enabled
|
||||||
enableGroupNameRoute?: boolean; // Controls whether group routing by name is allowed
|
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
|
// Add other system configuration sections here in the future
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user