Compare commits

...

13 Commits

Author SHA1 Message Date
samanhappy
7af3c8a2ba feat: add installation configuration support with pythonIndexUrl in settings (#67) 2025-05-10 21:33:35 +08:00
samanhappy
7c43ca359e feat: add acknowledgment section to README.zh.md (#68)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-10 21:33:22 +08:00
samanhappy
2bb6302cbc feat: enhance user experience with version info (#69)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-10 21:33:05 +08:00
samanhappy
3a3f6c984c Create FUNDING.yml (#65) 2025-05-10 11:05:07 +08:00
samanhappy
e8bc053788 fix: update react-router-dom to version 7.6.0 in package.json and pnpm-lock.yaml (#63) 2025-05-09 22:06:51 +08:00
samanhappy
bb674236c7 feat: update Node.js and pnpm versions in CI workflow; add packageManager field in package.json (#62) 2025-05-09 21:34:25 +08:00
samanhappy
0f5dfbe419 feat: support server-specified endpoint (#58) 2025-05-09 21:25:44 +08:00
samanhappy
74d1ca6a87 fix: remove unnecessary dependency from fetchMarketServers to improve performance (#56) 2025-05-09 21:24:27 +08:00
dependabot[bot]
eab421c753 chore(deps-dev): bump vite from 5.4.18 to 5.4.19 (#51)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 20:58:54 +08:00
samanhappy
886ca44681 Implement code changes to enhance functionality and improve performance (#52) 2025-05-05 20:54:11 +08:00
samanhappy
3d4baeef1a feat: update dependencies in pnpm-lock.yaml for improved functionality and security (#50) 2025-05-05 19:44:25 +08:00
samanhappy
4379513a35 feat: add GitHub Actions workflow for publishing to NPM (#49) 2025-05-05 19:39:09 +08:00
samanhappy
9a06bae225 fix: improve error handling in CallToolRequest by providing detailed error messages (#48) 2025-05-05 19:09:02 +08:00
25 changed files with 804 additions and 269 deletions

15
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: samanhappy
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

58
.github/workflows/npm-publish.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Publish to NPM
on:
push:
tags: ['v*.*.*']
jobs:
publish-npm:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 10
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Update version from tag
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "Updating package.json version to $VERSION"
jq ".version = \"$VERSION\"" package.json > package.json.tmp
mv package.json.tmp package.json
echo "Updated version in package.json:"
grep -m 1 "version" package.json
- name: Build package
run: pnpm build
- name: Publish to NPM
run: pnpm publish --no-git-checks --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -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

View File

@@ -115,6 +115,15 @@ Where `{group}` is the ID or name of the group you created in the dashboard. Thi
- Isolate different AI tools to access only relevant servers
- Implement more granular access control for different environments or teams
**Server-Specific Endpoints**:
For direct access to individual servers, use the server-specific HTTP endpoint:
```
http://localhost:3000/mcp/{server}
```
Where `{server}` is the name of the server you want to connect to. This allows you to access a specific MCP server directly.
> **Note**: If the server name and group name are the same, the group name will take precedence.
### SSE Endpoint (Deprecated in Future)
Connect AI clients (e.g., Claude Desktop, Cursor, DeepChat, etc.) via:
@@ -127,6 +136,11 @@ For targeted access to specific server groups, use the group-based SSE endpoint:
http://localhost:3000/sse/{group}
```
For direct access to individual servers, use the server-specific SSE endpoint:
```
http://localhost:3000/sse/{server}
```
## 🧑‍💻 Local Development
```bash

View File

@@ -68,11 +68,13 @@ MCPHub 是一个统一的 MCPModel Context Protocol模型上下文协议
### Docker 部署
**推荐**:挂载自定义配置:
```bash
docker run -p 3000:3000 -v $(pwd)/mcp_settings.json:/app/mcp_settings.json samanhappy/mcphub
```
或使用默认配置运行:
```bash
docker run -p 3000:3000 samanhappy/mcphub
```
@@ -80,22 +82,28 @@ docker run -p 3000:3000 samanhappy/mcphub
### 访问控制台
打开 `http://localhost:3000`,使用您的账号登录。
> **提示**:默认用户名/密码为 `admin` / `admin123`。
**控制台功能**
- 实时监控所有 MCP 服务器状态
- 启用/禁用或重新配置服务器
- 分组管理,组织服务器访问
- 用户管理,设定权限
### 支持流式的 HTTP 端点
> 截至目前,各家 AI 客户端对流式的 HTTP 端点支持不一,如果遇到问题,可以使用 SSE 端点或者等待更新。
通过以下地址连接 AI 客户端(如 Claude Desktop、Cursor、DeepChat 等):
```
http://localhost:3000/mcp
```
这个端点为所有 MCP 服务器提供统一的流式 HTTP 接口。它允许您:
- 向任何配置的 MCP 服务器发送请求
- 实时接收响应
- 轻松与各种 AI 客户端和工具集成
@@ -104,28 +112,50 @@ http://localhost:3000/mcp
**基于分组的 HTTP 端点(推荐)**
![分组](assets/group.zh.png)
要针对特定服务器分组进行访问,请使用基于分组的 HTTP 端点:
```
http://localhost:3000/mcp/{group}
```
其中 `{group}` 是您在控制面板中创建的分组 ID 或名称。这样做可以:
- 连接到按用例组织的特定 MCP 服务器子集
- 隔离不同的 AI 工具,使其只能访问相关服务器
- 为不同环境或团队实现更精细的访问控制
- 通过分组名称轻松识别和管理服务器
- 允许不同的 AI 客户端使用相同的端点,简化集成过程
### SSE 端点集成 (即将废弃)
**针对特定服务器的 HTTP 端点**
要针对特定服务器进行访问,请使用以下格式:
```
http://localhost:3000/mcp/{server}
```
其中 `{server}` 是您要连接的服务器名称。这样做可以直接访问特定的 MCP 服务器。
> **提示**:如果服务器名称和分组名称相同,则分组名称优先。
### SSE 端点集成 (未来可能废弃)
通过以下地址连接 AI 客户端(如 Claude Desktop、Cursor、DeepChat 等):
```
http://localhost:3000/sse
```
要针对特定服务器分组进行访问,请使用基于分组的 SSE 端点:
```
http://localhost:3000/sse/{group}
```
要针对特定服务器进行访问,请使用以下格式:
```
http://localhost:3000/sse/{server}
```
## 🧑‍💻 本地开发
```bash
@@ -142,6 +172,7 @@ pnpm dev
## 🛠️ 常见问题
### 使用 nginx 反向代理
如果您在使用 nginx 反向代理 MCPHub请确保在 nginx 配置中添加以下内容:
```nginx
@@ -168,6 +199,13 @@ proxy_buffering off
<img src="assets/wexin.png" width="350">
如果觉得项目有帮助,不妨请我喝杯咖啡 ☕️
<img src="assets/reward.png" width="350">
## 致谢
感谢以下人员的赞赏:小白、琛。你们的支持是我继续前进的动力!
## 📄 许可证
本项目采用 [Apache 2.0 许可证](LICENSE)。

BIN
assets/reward.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -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 "$@"

View File

@@ -1,6 +1,6 @@
import { ChevronDown, ChevronRight, Edit, Trash, Copy, Check } from 'lucide-react'
import { ChevronDown, ChevronRight, Edit, Trash, Copy, Check, User, Settings, LogOut, Info } from 'lucide-react'
export { ChevronDown, ChevronRight, Edit, Trash, Copy, Check }
export { ChevronDown, ChevronRight, Edit, Trash, Copy, Check, User, Settings, LogOut, Info }
const LucideIcons = {
ChevronDown,
@@ -8,7 +8,11 @@ const LucideIcons = {
Edit,
Trash,
Copy,
Check
Check,
User,
Settings,
LogOut,
Info
}
export default LucideIcons

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext';
import ThemeSwitch from '@/components/ui/ThemeSwitch';
@@ -10,20 +9,14 @@ interface HeaderProps {
const Header: React.FC<HeaderProps> = ({ onToggleSidebar }) => {
const { t } = useTranslation();
const navigate = useNavigate();
const { auth, logout } = useAuth();
const handleLogout = () => {
logout();
navigate('/login');
};
const { auth } = useAuth();
return (
<header className="bg-white dark:bg-gray-800 shadow-sm z-10">
<div className="flex justify-between items-center px-4 py-3">
<div className="flex items-center">
{/* 侧边栏切换按钮 */}
<button
<button
onClick={onToggleSidebar}
className="p-2 rounded-md text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none"
aria-label={t('app.toggleSidebar')}
@@ -32,30 +25,15 @@ const Header: React.FC<HeaderProps> = ({ onToggleSidebar }) => {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
{/* 应用标题 */}
<h1 className="ml-4 text-xl font-bold text-gray-900 dark:text-white">{t('app.title')}</h1>
</div>
{/* 用户信息和操作 */}
{/* Theme Switch */}
<div className="flex items-center space-x-4">
{/* Theme Switch */}
<ThemeSwitch />
{auth.user && (
<span className="text-sm text-gray-700 dark:text-gray-300">
{t('app.welcomeUser', { username: auth.user.username })}
</span>
)}
<div className="flex space-x-2">
<button
onClick={handleLogout}
className="px-3 py-1.5 bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100 rounded hover:bg-red-200 dark:hover:bg-red-800 text-sm"
>
{t('app.logout')}
</button>
</div>
</div>
</div>
</header>

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { NavLink, useLocation } from 'react-router-dom';
import UserProfileMenu from '@/components/ui/UserProfileMenu';
interface SidebarProps {
collapsed: boolean;
@@ -16,6 +17,9 @@ const Sidebar: React.FC<SidebarProps> = ({ collapsed }) => {
const { t } = useTranslation();
const location = useLocation();
// Application version from package.json (accessed via Vite environment variables)
const appVersion = import.meta.env.PACKAGE_VERSION as string;
// Menu item configuration
const menuItems: MenuItem[] = [
{
@@ -64,42 +68,41 @@ const Sidebar: React.FC<SidebarProps> = ({ collapsed }) => {
</svg>
),
},
{
path: '/settings',
label: t('nav.settings'),
icon: (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
</svg>
),
},
];
return (
<aside
className={`bg-white dark:bg-gray-800 shadow-sm transition-all duration-300 ease-in-out ${
className={`bg-white dark:bg-gray-800 shadow-sm transition-all duration-300 ease-in-out flex flex-col h-full relative ${
collapsed ? 'w-16' : 'w-64'
}`}
>
<nav className="p-3 space-y-1">
{menuItems.map((item) => (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
`flex items-center px-3 py-2 rounded-md transition-colors ${
isActive
? 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
}`
}
end={item.path === '/'}
>
<span className="flex-shrink-0">{item.icon}</span>
{!collapsed && <span className="ml-3">{item.label}</span>}
</NavLink>
))}
</nav>
{/* Scrollable navigation area */}
<div className="overflow-y-auto flex-grow">
<nav className="p-3 space-y-1">
{menuItems.map((item) => (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
`flex items-center px-3 py-2 rounded-md transition-colors ${
isActive
? 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
}`
}
end={item.path === '/'}
>
<span className="flex-shrink-0">{item.icon}</span>
{!collapsed && <span className="ml-3">{item.label}</span>}
</NavLink>
))}
</nav>
</div>
{/* User profile menu fixed at the bottom */}
<div className="p-3 bg-white dark:bg-gray-800">
<UserProfileMenu collapsed={collapsed} version={appVersion} />
</div>
</aside>
);
};

View File

@@ -0,0 +1,92 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { X } from 'lucide-react';
interface AboutDialogProps {
isOpen: boolean;
onClose: () => void;
version: string;
}
const AboutDialog: React.FC<AboutDialogProps> = ({ isOpen, onClose, version }) => {
const { t } = useTranslation();
const [hasNewVersion, setHasNewVersion] = useState(false);
const [latestVersion, setLatestVersion] = useState("");
// Check if there's a new version available
// In a real application, this would make an API call to check for updates
useEffect(() => {
if (isOpen) {
// Mock implementation - in real implementation, you would fetch from an API
const checkForUpdates = async () => {
try {
// This is a placeholder - in a real app you would call an API
// const response = await fetch('/api/check-updates');
// const data = await response.json();
// For demo purposes, we'll just pretend there's a new version
// TODO: Replace this placeholder logic with a real API call to check for updates
setHasNewVersion(false);
setLatestVersion("0.0.28"); // Just a higher version for demo
} catch (error) {
console.error('Failed to check for updates:', error);
}
};
checkForUpdates();
}
}, [isOpen]);
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 bg-opacity-30 z-50 flex items-center justify-center p-4">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg max-w-md w-full">
<div className="p-6 relative">
{/* Close button (X) in the top-right corner */}
<button
onClick={onClose}
className="absolute top-4 right-4 text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400"
aria-label={t('common.close')}
>
<X className="h-5 w-5" />
</button>
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
{t('about.title')}
</h3>
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-gray-700 dark:text-gray-300">
{t('about.currentVersion')}:
</span>
<span className="font-medium text-gray-900 dark:text-gray-100">
{version}
</span>
</div>
{hasNewVersion && (
<div className="bg-blue-50 dark:bg-blue-900 p-3 rounded">
<div className="flex items-start">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-blue-600 dark:text-blue-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div className="ml-3">
<p className="text-sm font-medium text-blue-800 dark:text-blue-200">
{t('about.newVersion')} (v{latestVersion})
</p>
</div>
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default AboutDialog;

View File

@@ -0,0 +1,127 @@
import React, { useState, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext';
import { User, Settings, LogOut, Info } from 'lucide-react';
import AboutDialog from './AboutDialog';
interface UserProfileMenuProps {
collapsed: boolean;
version: string;
}
const UserProfileMenu: React.FC<UserProfileMenuProps> = ({ collapsed, version }) => {
const { t } = useTranslation();
const navigate = useNavigate();
const { auth, logout } = useAuth();
const [isOpen, setIsOpen] = useState(false);
const [showNewVersionInfo, setShowNewVersionInfo] = useState(false);
const [showAboutDialog, setShowAboutDialog] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
// Check for new version - this could be replaced with a real API call
useEffect(() => {
// Mock implementation - in a real app, you would check against an API
const checkForNewVersion = async () => {
// For demo purposes, we're just showing the notification
// In real implementation, you would compare current version with latest version
setShowNewVersionInfo(false);
};
checkForNewVersion();
}, []);
// Close the menu when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
const handleSettingsClick = () => {
navigate('/settings');
setIsOpen(false);
};
const handleLogoutClick = () => {
logout();
navigate('/login');
};
const handleAboutClick = () => {
setShowAboutDialog(true);
setIsOpen(false);
};
return (
<div ref={menuRef} className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className={`flex ${collapsed ? 'justify-center' : 'items-center'} w-full p-2 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors rounded-md ${isOpen ? 'bg-gray-100 dark:bg-gray-700' : ''
}`}
>
<div className="flex-shrink-0 relative">
<div className="w-7 h-7 flex items-center justify-center rounded-full border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-700">
<User className="h-4 w-4 text-gray-700 dark:text-gray-300" />
</div>
{showNewVersionInfo && (
<span className="absolute -top-1 -right-1 block w-2 h-2 bg-red-500 rounded-full"></span>
)}
</div>
{!collapsed && (
<div className="ml-3 flex flex-col items-start">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
{auth.user?.username || t('auth.user')}
</span>
</div>
)}
</button>
{isOpen && (
<div className="absolute top-0 transform -translate-y-full left-0 w-48 bg-white dark:bg-gray-800 shadow-lg rounded-md py-1 z-50">
<button
onClick={handleSettingsClick}
className="flex items-center w-full px-4 py-2 text-left text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
>
<Settings className="h-4 w-4 mr-2" />
{t('nav.settings')}
</button>
<button
onClick={handleAboutClick}
className="flex items-center w-full px-4 py-2 text-left text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 relative"
>
<Info className="h-4 w-4 mr-2" />
{t('about.title')}
&nbsp;({version})
{showNewVersionInfo && (
<span className="absolute top-2 right-4 block w-2 h-2 bg-red-500 rounded-full"></span>
)}
</button>
<button
onClick={handleLogoutClick}
className="flex items-center w-full px-4 py-2 text-left text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
>
<LogOut className="h-4 w-4 mr-2" />
{t('app.logout')}
</button>
</div>
)}
{/* About dialog */}
<AboutDialog
isOpen={showAboutDialog}
onClose={() => setShowAboutDialog(false)}
version={version}
/>
</div>
);
};
export default UserProfileMenu;

View File

@@ -52,7 +52,7 @@ export const useMarketData = () => {
} finally {
setLoading(false);
}
}, [t, currentPage]);
}, [t]);
// Apply pagination to data
const applyPagination = useCallback((data: MarketServer[], page: number, itemsPerPage = serversPerPage) => {

View File

@@ -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<InstallConfig>({
pythonIndexUrl: '',
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(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,
};
};

View File

@@ -13,7 +13,7 @@ const MainLayout: React.FC = () => {
};
return (
<div className="flex flex-col min-h-screen bg-gray-100 dark:bg-gray-900">
<div className="flex flex-col h-screen bg-gray-100 dark:bg-gray-900">
{/* 顶部导航 */}
<Header onToggleSidebar={toggleSidebar} />

View File

@@ -12,6 +12,16 @@
"welcomeUser": "Welcome, {{username}}",
"name": "MCP Hub"
},
"about": {
"title": "About",
"versionInfo": "MCP Hub Version: {{version}}",
"newVersion": "New version available!",
"currentVersion": "Current version"
},
"profile": {
"viewProfile": "View profile",
"userCenter": "User Center"
},
"theme": {
"title": "Theme",
"light": "Light",
@@ -105,7 +115,8 @@
"delete": "Delete",
"copy": "Copy",
"copySuccess": "Copied to clipboard",
"copyFailed": "Copy failed"
"copyFailed": "Copy failed",
"close": "Close"
},
"nav": {
"dashboard": "Dashboard",
@@ -137,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)"
@@ -232,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"
}
}

View File

@@ -12,6 +12,16 @@
"welcomeUser": "欢迎, {{username}}",
"name": "MCP Hub"
},
"about": {
"title": "关于",
"versionInfo": "MCP Hub 版本: {{version}}",
"newVersion": "有新版本可用!",
"currentVersion": "当前版本"
},
"profile": {
"viewProfile": "查看个人中心",
"userCenter": "个人中心"
},
"theme": {
"title": "主题",
"light": "浅色",
@@ -106,7 +116,8 @@
"delete": "删除",
"copy": "复制",
"copySuccess": "已复制到剪贴板",
"copyFailed": "复制失败"
"copyFailed": "复制失败",
"close": "关闭"
},
"nav": {
"dashboard": "仪表盘",
@@ -135,7 +146,8 @@
"account": "账户设置",
"password": "修改密码",
"appearance": "外观",
"routeConfig": "路由配置"
"routeConfig": "路由配置",
"installConfig": "安装配置"
},
"groups": {
"title": "分组管理"
@@ -230,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": "系统配置更新成功"
}
}

View File

@@ -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 = () => {
)}
</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 */}
<div className="bg-white shadow rounded-lg py-4 px-6 mb-6">
<div

9
frontend/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
/// <reference types="vite/client" />
interface ImportMeta {
readonly env: {
readonly PACKAGE_VERSION: string;
// Add other custom env variables here if needed
[key: string]: any;
};
}

View File

@@ -2,6 +2,13 @@ import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import tailwindcss from '@tailwindcss/vite';
// Import the package.json to get the version
import { readFileSync } from 'fs';
// Get package.json version
const packageJson = JSON.parse(
readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8')
);
// https://vitejs.dev/config/
export default defineConfig({
@@ -11,6 +18,10 @@ export default defineConfig({
'@': path.resolve(__dirname, './src'),
},
},
define: {
// Make package version available as global variable
'import.meta.env.PACKAGE_VERSION': JSON.stringify(packageJson.version),
},
server: {
proxy: {
'/api': {

View File

@@ -1,6 +1,6 @@
{
"name": "@samanhappy/mcphub",
"version": "0.0.27",
"version": "0.5.4",
"description": "A hub server for mcp servers",
"main": "dist/index.js",
"type": "module",
@@ -79,7 +79,7 @@
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^15.4.1",
"react-router-dom": "^7.5.0",
"react-router-dom": "^7.6.0",
"tailwind-merge": "^3.1.0",
"tailwind-scrollbar-hide": "^2.0.0",
"tailwindcss": "^4.0.17",
@@ -92,5 +92,6 @@
},
"engines": {
"node": ">=16.0.0"
}
}
},
"packageManager": "pnpm@10.10.0+sha256.fa0f513aa8191764d2b6b432420788c270f07b4f999099b65bb2010eec702a30"
}

351
pnpm-lock.yaml generated
View File

@@ -11,39 +11,9 @@ importers:
'@modelcontextprotocol/sdk':
specifier: ^1.10.2
version: 1.10.2
'@radix-ui/react-accordion':
specifier: ^1.2.3
version: 1.2.3(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-slot':
specifier: ^1.1.2
version: 1.1.2(@types/react@19.0.12)(react@19.1.0)
'@shadcn/ui':
specifier: ^0.0.4
version: 0.0.4
'@tailwindcss/vite':
specifier: ^4.1.3
version: 4.1.3(vite@5.4.18(@types/node@20.17.28)(lightningcss@1.29.2))
'@types/react':
specifier: ^19.0.12
version: 19.0.12
'@types/react-dom':
specifier: ^19.0.4
version: 19.0.4(@types/react@19.0.12)
'@types/uuid':
specifier: ^10.0.0
version: 10.0.0
autoprefixer:
specifier: ^10.4.21
version: 10.4.21(postcss@8.5.3)
bcryptjs:
specifier: ^3.0.2
version: 3.0.2
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
clsx:
specifier: ^2.1.1
version: 2.1.1
dotenv:
specifier: ^16.3.1
version: 16.4.7
@@ -53,55 +23,28 @@ importers:
express-validator:
specifier: ^7.2.1
version: 7.2.1
i18next:
specifier: ^24.2.3
version: 24.2.3(typescript@5.8.2)
i18next-browser-languagedetector:
specifier: ^8.0.4
version: 8.0.4
jsonwebtoken:
specifier: ^9.0.2
version: 9.0.2
lucide-react:
specifier: ^0.486.0
version: 0.486.0(react@19.1.0)
next:
specifier: ^15.2.4
version: 15.2.4(@babel/core@7.26.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
postcss:
specifier: ^8.5.3
version: 8.5.3
react:
specifier: ^19.1.0
version: 19.1.0
react-dom:
specifier: ^19.1.0
version: 19.1.0(react@19.1.0)
react-i18next:
specifier: ^15.4.1
version: 15.4.1(i18next@24.2.3(typescript@5.8.2))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-router-dom:
specifier: ^7.5.0
version: 7.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
tailwind-merge:
specifier: ^3.1.0
version: 3.1.0
tailwind-scrollbar-hide:
specifier: ^2.0.0
version: 2.0.0(tailwindcss@4.0.17)
tailwindcss:
specifier: ^4.0.17
version: 4.0.17
uuid:
specifier: ^11.1.0
version: 11.1.0
zod:
specifier: ^3.24.2
version: 3.24.2
devDependencies:
'@radix-ui/react-accordion':
specifier: ^1.2.3
version: 1.2.3(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-slot':
specifier: ^1.1.2
version: 1.1.2(@types/react@19.0.12)(react@19.1.0)
'@shadcn/ui':
specifier: ^0.0.4
version: 0.0.4
'@tailwindcss/postcss':
specifier: ^4.1.3
version: 4.1.3
'@tailwindcss/vite':
specifier: ^4.1.3
version: 4.1.3(vite@5.4.19(@types/node@20.17.28)(lightningcss@1.29.2))
'@types/bcryptjs':
specifier: ^3.0.0
version: 3.0.0
@@ -117,6 +60,15 @@ importers:
'@types/node':
specifier: ^20.8.2
version: 20.17.28
'@types/react':
specifier: ^19.0.12
version: 19.0.12
'@types/react-dom':
specifier: ^19.0.4
version: 19.0.4(@types/react@19.0.12)
'@types/uuid':
specifier: ^10.0.0
version: 10.0.0
'@typescript-eslint/eslint-plugin':
specifier: ^6.7.4
version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1)(typescript@5.8.2)
@@ -125,19 +77,64 @@ importers:
version: 6.21.0(eslint@8.57.1)(typescript@5.8.2)
'@vitejs/plugin-react':
specifier: ^4.2.1
version: 4.3.4(vite@5.4.18(@types/node@20.17.28)(lightningcss@1.29.2))
version: 4.3.4(vite@5.4.19(@types/node@20.17.28)(lightningcss@1.29.2))
autoprefixer:
specifier: ^10.4.21
version: 10.4.21(postcss@8.5.3)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
clsx:
specifier: ^2.1.1
version: 2.1.1
concurrently:
specifier: ^8.2.2
version: 8.2.2
eslint:
specifier: ^8.50.0
version: 8.57.1
i18next:
specifier: ^24.2.3
version: 24.2.3(typescript@5.8.2)
i18next-browser-languagedetector:
specifier: ^8.0.4
version: 8.0.4
jest:
specifier: ^29.7.0
version: 29.7.0(@types/node@20.17.28)(ts-node@10.9.2(@types/node@20.17.28)(typescript@5.8.2))
lucide-react:
specifier: ^0.486.0
version: 0.486.0(react@19.1.0)
next:
specifier: ^15.2.4
version: 15.2.4(@babel/core@7.26.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
postcss:
specifier: ^8.5.3
version: 8.5.3
prettier:
specifier: ^3.0.3
version: 3.5.3
react:
specifier: ^19.1.0
version: 19.1.0
react-dom:
specifier: ^19.1.0
version: 19.1.0(react@19.1.0)
react-i18next:
specifier: ^15.4.1
version: 15.4.1(i18next@24.2.3(typescript@5.8.2))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-router-dom:
specifier: ^7.6.0
version: 7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
tailwind-merge:
specifier: ^3.1.0
version: 3.1.0
tailwind-scrollbar-hide:
specifier: ^2.0.0
version: 2.0.0(tailwindcss@4.0.17)
tailwindcss:
specifier: ^4.0.17
version: 4.0.17
ts-jest:
specifier: ^29.1.1
version: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@20.17.28)(ts-node@10.9.2(@types/node@20.17.28)(typescript@5.8.2)))(typescript@5.8.2)
@@ -152,7 +149,10 @@ importers:
version: 5.8.2
vite:
specifier: ^5.4.18
version: 5.4.18(@types/node@20.17.28)(lightningcss@1.29.2)
version: 5.4.19(@types/node@20.17.28)(lightningcss@1.29.2)
zod:
specifier: ^3.24.2
version: 3.24.2
packages:
@@ -1074,103 +1074,103 @@ packages:
'@types/react':
optional: true
'@rollup/rollup-android-arm-eabi@4.40.0':
resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==}
'@rollup/rollup-android-arm-eabi@4.40.1':
resolution: {integrity: sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.40.0':
resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==}
'@rollup/rollup-android-arm64@4.40.1':
resolution: {integrity: sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.40.0':
resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==}
'@rollup/rollup-darwin-arm64@4.40.1':
resolution: {integrity: sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.40.0':
resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==}
'@rollup/rollup-darwin-x64@4.40.1':
resolution: {integrity: sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.40.0':
resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==}
'@rollup/rollup-freebsd-arm64@4.40.1':
resolution: {integrity: sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.40.0':
resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==}
'@rollup/rollup-freebsd-x64@4.40.1':
resolution: {integrity: sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.40.0':
resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==}
'@rollup/rollup-linux-arm-gnueabihf@4.40.1':
resolution: {integrity: sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.40.0':
resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==}
'@rollup/rollup-linux-arm-musleabihf@4.40.1':
resolution: {integrity: sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.40.0':
resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==}
'@rollup/rollup-linux-arm64-gnu@4.40.1':
resolution: {integrity: sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.40.0':
resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==}
'@rollup/rollup-linux-arm64-musl@4.40.1':
resolution: {integrity: sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-loongarch64-gnu@4.40.0':
resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==}
'@rollup/rollup-linux-loongarch64-gnu@4.40.1':
resolution: {integrity: sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==}
cpu: [loong64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.40.0':
resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==}
'@rollup/rollup-linux-powerpc64le-gnu@4.40.1':
resolution: {integrity: sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.40.0':
resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==}
'@rollup/rollup-linux-riscv64-gnu@4.40.1':
resolution: {integrity: sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-riscv64-musl@4.40.0':
resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==}
'@rollup/rollup-linux-riscv64-musl@4.40.1':
resolution: {integrity: sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.40.0':
resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==}
'@rollup/rollup-linux-s390x-gnu@4.40.1':
resolution: {integrity: sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.40.0':
resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==}
'@rollup/rollup-linux-x64-gnu@4.40.1':
resolution: {integrity: sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.40.0':
resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==}
'@rollup/rollup-linux-x64-musl@4.40.1':
resolution: {integrity: sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.40.0':
resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==}
'@rollup/rollup-win32-arm64-msvc@4.40.1':
resolution: {integrity: sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.40.0':
resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==}
'@rollup/rollup-win32-ia32-msvc@4.40.1':
resolution: {integrity: sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.40.0':
resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==}
'@rollup/rollup-win32-x64-msvc@4.40.1':
resolution: {integrity: sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==}
cpu: [x64]
os: [win32]
@@ -1308,9 +1308,6 @@ packages:
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
'@types/estree@1.0.7':
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
@@ -2805,6 +2802,7 @@ packages:
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
deprecated: Use your platform's native DOMException instead
node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
@@ -3031,15 +3029,15 @@ packages:
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
engines: {node: '>=0.10.0'}
react-router-dom@7.5.0:
resolution: {integrity: sha512-fFhGFCULy4vIseTtH5PNcY/VvDJK5gvOWcwJVHQp8JQcWVr85ENhJ3UpuF/zP1tQOIFYNRJHzXtyhU1Bdgw0RA==}
react-router-dom@7.6.0:
resolution: {integrity: sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
react-router@7.5.0:
resolution: {integrity: sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g==}
react-router@7.6.0:
resolution: {integrity: sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
@@ -3109,8 +3107,8 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rollup@4.40.0:
resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==}
rollup@4.40.1:
resolution: {integrity: sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -3419,9 +3417,6 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
turbo-stream@2.4.0:
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -3501,8 +3496,8 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
vite@5.4.18:
resolution: {integrity: sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==}
vite@5.4.19:
resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@@ -4438,64 +4433,64 @@ snapshots:
optionalDependencies:
'@types/react': 19.0.12
'@rollup/rollup-android-arm-eabi@4.40.0':
'@rollup/rollup-android-arm-eabi@4.40.1':
optional: true
'@rollup/rollup-android-arm64@4.40.0':
'@rollup/rollup-android-arm64@4.40.1':
optional: true
'@rollup/rollup-darwin-arm64@4.40.0':
'@rollup/rollup-darwin-arm64@4.40.1':
optional: true
'@rollup/rollup-darwin-x64@4.40.0':
'@rollup/rollup-darwin-x64@4.40.1':
optional: true
'@rollup/rollup-freebsd-arm64@4.40.0':
'@rollup/rollup-freebsd-arm64@4.40.1':
optional: true
'@rollup/rollup-freebsd-x64@4.40.0':
'@rollup/rollup-freebsd-x64@4.40.1':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.40.0':
'@rollup/rollup-linux-arm-gnueabihf@4.40.1':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.40.0':
'@rollup/rollup-linux-arm-musleabihf@4.40.1':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.40.0':
'@rollup/rollup-linux-arm64-gnu@4.40.1':
optional: true
'@rollup/rollup-linux-arm64-musl@4.40.0':
'@rollup/rollup-linux-arm64-musl@4.40.1':
optional: true
'@rollup/rollup-linux-loongarch64-gnu@4.40.0':
'@rollup/rollup-linux-loongarch64-gnu@4.40.1':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.40.0':
'@rollup/rollup-linux-powerpc64le-gnu@4.40.1':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.40.0':
'@rollup/rollup-linux-riscv64-gnu@4.40.1':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.40.0':
'@rollup/rollup-linux-riscv64-musl@4.40.1':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.40.0':
'@rollup/rollup-linux-s390x-gnu@4.40.1':
optional: true
'@rollup/rollup-linux-x64-gnu@4.40.0':
'@rollup/rollup-linux-x64-gnu@4.40.1':
optional: true
'@rollup/rollup-linux-x64-musl@4.40.0':
'@rollup/rollup-linux-x64-musl@4.40.1':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.40.0':
'@rollup/rollup-win32-arm64-msvc@4.40.1':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.40.0':
'@rollup/rollup-win32-ia32-msvc@4.40.1':
optional: true
'@rollup/rollup-win32-x64-msvc@4.40.0':
'@rollup/rollup-win32-x64-msvc@4.40.1':
optional: true
'@shadcn/ui@0.0.4':
@@ -4587,12 +4582,12 @@ snapshots:
postcss: 8.5.3
tailwindcss: 4.1.3
'@tailwindcss/vite@4.1.3(vite@5.4.18(@types/node@20.17.28)(lightningcss@1.29.2))':
'@tailwindcss/vite@4.1.3(vite@5.4.19(@types/node@20.17.28)(lightningcss@1.29.2))':
dependencies:
'@tailwindcss/node': 4.1.3
'@tailwindcss/oxide': 4.1.3
tailwindcss: 4.1.3
vite: 5.4.18(@types/node@20.17.28)(lightningcss@1.29.2)
vite: 5.4.19(@types/node@20.17.28)(lightningcss@1.29.2)
'@tsconfig/node10@1.0.11': {}
@@ -4636,8 +4631,6 @@ snapshots:
dependencies:
'@types/node': 20.17.28
'@types/cookie@0.6.0': {}
'@types/estree@1.0.7': {}
'@types/express-serve-static-core@4.19.6':
@@ -4817,14 +4810,14 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
'@vitejs/plugin-react@4.3.4(vite@5.4.18(@types/node@20.17.28)(lightningcss@1.29.2))':
'@vitejs/plugin-react@4.3.4(vite@5.4.19(@types/node@20.17.28)(lightningcss@1.29.2))':
dependencies:
'@babel/core': 7.26.10
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10)
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
vite: 5.4.18(@types/node@20.17.28)(lightningcss@1.29.2)
vite: 5.4.19(@types/node@20.17.28)(lightningcss@1.29.2)
transitivePeerDependencies:
- supports-color
@@ -6651,19 +6644,17 @@ snapshots:
react-refresh@0.14.2: {}
react-router-dom@7.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
react-router-dom@7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react-router: 7.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-router: 7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-router@7.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
react-router@7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@types/cookie': 0.6.0
cookie: 1.0.2
react: 19.1.0
set-cookie-parser: 2.7.1
turbo-stream: 2.4.0
optionalDependencies:
react-dom: 19.1.0(react@19.1.0)
@@ -6716,30 +6707,30 @@ snapshots:
dependencies:
glob: 7.2.3
rollup@4.40.0:
rollup@4.40.1:
dependencies:
'@types/estree': 1.0.7
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.40.0
'@rollup/rollup-android-arm64': 4.40.0
'@rollup/rollup-darwin-arm64': 4.40.0
'@rollup/rollup-darwin-x64': 4.40.0
'@rollup/rollup-freebsd-arm64': 4.40.0
'@rollup/rollup-freebsd-x64': 4.40.0
'@rollup/rollup-linux-arm-gnueabihf': 4.40.0
'@rollup/rollup-linux-arm-musleabihf': 4.40.0
'@rollup/rollup-linux-arm64-gnu': 4.40.0
'@rollup/rollup-linux-arm64-musl': 4.40.0
'@rollup/rollup-linux-loongarch64-gnu': 4.40.0
'@rollup/rollup-linux-powerpc64le-gnu': 4.40.0
'@rollup/rollup-linux-riscv64-gnu': 4.40.0
'@rollup/rollup-linux-riscv64-musl': 4.40.0
'@rollup/rollup-linux-s390x-gnu': 4.40.0
'@rollup/rollup-linux-x64-gnu': 4.40.0
'@rollup/rollup-linux-x64-musl': 4.40.0
'@rollup/rollup-win32-arm64-msvc': 4.40.0
'@rollup/rollup-win32-ia32-msvc': 4.40.0
'@rollup/rollup-win32-x64-msvc': 4.40.0
'@rollup/rollup-android-arm-eabi': 4.40.1
'@rollup/rollup-android-arm64': 4.40.1
'@rollup/rollup-darwin-arm64': 4.40.1
'@rollup/rollup-darwin-x64': 4.40.1
'@rollup/rollup-freebsd-arm64': 4.40.1
'@rollup/rollup-freebsd-x64': 4.40.1
'@rollup/rollup-linux-arm-gnueabihf': 4.40.1
'@rollup/rollup-linux-arm-musleabihf': 4.40.1
'@rollup/rollup-linux-arm64-gnu': 4.40.1
'@rollup/rollup-linux-arm64-musl': 4.40.1
'@rollup/rollup-linux-loongarch64-gnu': 4.40.1
'@rollup/rollup-linux-powerpc64le-gnu': 4.40.1
'@rollup/rollup-linux-riscv64-gnu': 4.40.1
'@rollup/rollup-linux-riscv64-musl': 4.40.1
'@rollup/rollup-linux-s390x-gnu': 4.40.1
'@rollup/rollup-linux-x64-gnu': 4.40.1
'@rollup/rollup-linux-x64-musl': 4.40.1
'@rollup/rollup-win32-arm64-msvc': 4.40.1
'@rollup/rollup-win32-ia32-msvc': 4.40.1
'@rollup/rollup-win32-x64-msvc': 4.40.1
fsevents: 2.3.3
router@2.2.0:
@@ -7088,8 +7079,6 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
turbo-stream@2.4.0: {}
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
@@ -7149,11 +7138,11 @@ snapshots:
vary@1.1.2: {}
vite@5.4.18(@types/node@20.17.28)(lightningcss@1.29.2):
vite@5.4.19(@types/node@20.17.28)(lightningcss@1.29.2):
dependencies:
esbuild: 0.21.5
postcss: 8.5.3
rollup: 4.40.0
rollup: 4.40.1
optionalDependencies:
'@types/node': 20.17.28
fsevents: 2.3.3

View File

@@ -247,9 +247,10 @@ export const toggleServer = async (req: Request, res: Response): Promise<void> =
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)) {

View File

@@ -79,6 +79,13 @@ export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] =>
} else if (conf.command && conf.args) {
const env: Record<string, string> = 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,
@@ -347,6 +354,7 @@ export const createMcpServer = (name: string, version: string): Server => {
if (serverInfo.enabled === false) return false;
if (!group) return true;
const serversInGroup = getServersInGroup(group);
if (!serversInGroup || serversInGroup.length === 0) return serverInfo.name === group;
return serversInGroup.includes(serverInfo.name);
});
@@ -365,9 +373,6 @@ export const createMcpServer = (name: string, version: string): Server => {
server.setRequestHandler(CallToolRequestSchema, async (request, _) => {
console.log(`Handling CallToolRequest for tool: ${request.params.name}`);
try {
if (!request.params.arguments) {
throw new Error('Arguments are required');
}
const serverInfo = getServerByTool(request.params.name);
if (!serverInfo) {
throw new Error(`Server not found: ${request.params.name}`);
@@ -381,7 +386,15 @@ export const createMcpServer = (name: string, version: string): Server => {
return result;
} catch (error) {
console.error(`Error handling CallToolRequest: ${error}`);
return { error: `Failed to call tool: ${error}` };
return {
content: [
{
type: 'text',
text: `Error: ${error}`,
},
],
isError: true,
};
}
});
return server;

View File

@@ -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
};
}