feat(MCP): add registryUrl support for package management (#4200)
This commit is contained in:
parent
b43ecb75f5
commit
8c5f61d407
@ -21,6 +21,7 @@ class McpService {
|
|||||||
baseUrl: server.baseUrl,
|
baseUrl: server.baseUrl,
|
||||||
command: server.command,
|
command: server.command,
|
||||||
args: server.args,
|
args: server.args,
|
||||||
|
registryUrl: server.registryUrl,
|
||||||
env: server.env,
|
env: server.env,
|
||||||
id: server.id
|
id: server.id
|
||||||
})
|
})
|
||||||
@ -94,6 +95,23 @@ class McpService {
|
|||||||
|
|
||||||
Logger.info(`[MCP] Starting server with command: ${cmd} ${args ? args.join(' ') : ''}`)
|
Logger.info(`[MCP] Starting server with command: ${cmd} ${args ? args.join(' ') : ''}`)
|
||||||
|
|
||||||
|
if (server.registryUrl) {
|
||||||
|
if (cmd.includes('npx') || cmd.includes('bun') || cmd.includes('bunx')) {
|
||||||
|
server.env = {
|
||||||
|
...server.env,
|
||||||
|
NPM_CONFIG_REGISTRY: server.registryUrl
|
||||||
|
}
|
||||||
|
} else if (cmd.includes('uvx') || cmd.includes('uv')) {
|
||||||
|
server.env = {
|
||||||
|
...server.env,
|
||||||
|
UV_DEFAULT_INDEX: server.registryUrl,
|
||||||
|
PIP_INDEX_URL: server.registryUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger.info(`[MCP] Environment variables for server:`, server.env)
|
||||||
|
|
||||||
transport = new StdioClientTransport({
|
transport = new StdioClientTransport({
|
||||||
command: cmd,
|
command: cmd,
|
||||||
args,
|
args,
|
||||||
|
|||||||
@ -1045,7 +1045,10 @@
|
|||||||
"noToolsAvailable": "No tools available"
|
"noToolsAvailable": "No tools available"
|
||||||
},
|
},
|
||||||
"deleteServer": "Delete Server",
|
"deleteServer": "Delete Server",
|
||||||
"deleteServerConfirm": "Are you sure you want to delete this server?"
|
"deleteServerConfirm": "Are you sure you want to delete this server?",
|
||||||
|
"registry": "Package Registry",
|
||||||
|
"registryTooltip": "Choose the registry for package installation to resolve network issues with the default registry.",
|
||||||
|
"registryDefault": "Default"
|
||||||
},
|
},
|
||||||
"messages.divider": "Show divider between messages",
|
"messages.divider": "Show divider between messages",
|
||||||
"messages.grid_columns": "Message grid display columns",
|
"messages.grid_columns": "Message grid display columns",
|
||||||
|
|||||||
@ -1044,7 +1044,10 @@
|
|||||||
"noToolsAvailable": "利用可能なツールはありません"
|
"noToolsAvailable": "利用可能なツールはありません"
|
||||||
},
|
},
|
||||||
"deleteServer": "サーバーを削除",
|
"deleteServer": "サーバーを削除",
|
||||||
"deleteServerConfirm": "このサーバーを削除してもよろしいですか?"
|
"deleteServerConfirm": "このサーバーを削除してもよろしいですか?",
|
||||||
|
"registry": "パッケージ管理レジストリ",
|
||||||
|
"registryTooltip": "デフォルトのレジストリでネットワークの問題が発生した場合、パッケージインストールに使用するレジストリを選択してください。",
|
||||||
|
"registryDefault": "デフォルト"
|
||||||
},
|
},
|
||||||
"messages.divider": "メッセージ間に区切り線を表示",
|
"messages.divider": "メッセージ間に区切り線を表示",
|
||||||
"messages.grid_columns": "メッセージグリッドの表示列数",
|
"messages.grid_columns": "メッセージグリッドの表示列数",
|
||||||
|
|||||||
@ -1044,7 +1044,10 @@
|
|||||||
"noToolsAvailable": "нет доступных инструментов"
|
"noToolsAvailable": "нет доступных инструментов"
|
||||||
},
|
},
|
||||||
"deleteServer": "Удалить сервер",
|
"deleteServer": "Удалить сервер",
|
||||||
"deleteServerConfirm": "Вы уверены, что хотите удалить этот сервер?"
|
"deleteServerConfirm": "Вы уверены, что хотите удалить этот сервер?",
|
||||||
|
"registry": "Реестр пакетов",
|
||||||
|
"registryTooltip": "Выберите реестр для установки пакетов, если возникают проблемы с сетью при использовании реестра по умолчанию.",
|
||||||
|
"registryDefault": "По умолчанию"
|
||||||
},
|
},
|
||||||
"messages.divider": "Показывать разделитель между сообщениями",
|
"messages.divider": "Показывать разделитель между сообщениями",
|
||||||
"messages.grid_columns": "Количество столбцов сетки сообщений",
|
"messages.grid_columns": "Количество столбцов сетки сообщений",
|
||||||
|
|||||||
@ -1045,7 +1045,10 @@
|
|||||||
"noToolsAvailable": "没有可用工具"
|
"noToolsAvailable": "没有可用工具"
|
||||||
},
|
},
|
||||||
"deleteServer": "删除服务器",
|
"deleteServer": "删除服务器",
|
||||||
"deleteServerConfirm": "确定要删除此服务器吗?"
|
"deleteServerConfirm": "确定要删除此服务器吗?",
|
||||||
|
"registry": "包管理源",
|
||||||
|
"registryTooltip": "选择用于安装包的源,以解决默认源的网络问题。",
|
||||||
|
"registryDefault": "默认"
|
||||||
},
|
},
|
||||||
"messages.divider": "消息分割线",
|
"messages.divider": "消息分割线",
|
||||||
"messages.grid_columns": "消息网格展示列数",
|
"messages.grid_columns": "消息网格展示列数",
|
||||||
|
|||||||
@ -1044,7 +1044,10 @@
|
|||||||
"noToolsAvailable": "沒有可用工具"
|
"noToolsAvailable": "沒有可用工具"
|
||||||
},
|
},
|
||||||
"deleteServer": "刪除伺服器",
|
"deleteServer": "刪除伺服器",
|
||||||
"deleteServerConfirm": "確定要刪除此伺服器嗎?"
|
"deleteServerConfirm": "確定要刪除此伺服器嗎?",
|
||||||
|
"registry": "套件管理源",
|
||||||
|
"registryTooltip": "選擇用於安裝套件的源,以解決預設源的網路問題。",
|
||||||
|
"registryDefault": "預設"
|
||||||
},
|
},
|
||||||
"messages.divider": "訊息間顯示分隔線",
|
"messages.divider": "訊息間顯示分隔線",
|
||||||
"messages.grid_columns": "訊息網格展示列數",
|
"messages.grid_columns": "訊息網格展示列數",
|
||||||
|
|||||||
@ -20,11 +20,26 @@ interface MCPFormValues {
|
|||||||
serverType: 'sse' | 'stdio'
|
serverType: 'sse' | 'stdio'
|
||||||
baseUrl?: string
|
baseUrl?: string
|
||||||
command?: string
|
command?: string
|
||||||
|
registryUrl?: string
|
||||||
args?: string
|
args?: string
|
||||||
env?: string
|
env?: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Registry {
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const NpmRegistry: Registry[] = [{ name: '淘宝 NPM Mirror', url: 'https://registry.npmmirror.com' }]
|
||||||
|
const PipRegistry: Registry[] = [
|
||||||
|
{ name: '清华大学', url: 'https://pypi.tuna.tsinghua.edu.cn/simple' },
|
||||||
|
{ name: '阿里云', url: 'http://mirrors.aliyun.com/pypi/simple/' },
|
||||||
|
{ name: '中国科学技术大学', url: 'https://mirrors.ustc.edu.cn/pypi/simple/' },
|
||||||
|
{ name: '华为云', url: 'https://repo.huaweicloud.com/repository/pypi/simple/' },
|
||||||
|
{ name: '腾讯云', url: 'https://mirrors.cloud.tencent.com/pypi/simple/' }
|
||||||
|
]
|
||||||
|
|
||||||
const McpSettings: React.FC<Props> = ({ server }) => {
|
const McpSettings: React.FC<Props> = ({ server }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { deleteMCPServer } = useMCPServers()
|
const { deleteMCPServer } = useMCPServers()
|
||||||
@ -35,36 +50,42 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
const [loadingServer, setLoadingServer] = useState<string | null>(null)
|
const [loadingServer, setLoadingServer] = useState<string | null>(null)
|
||||||
const { updateMCPServer } = useMCPServers()
|
const { updateMCPServer } = useMCPServers()
|
||||||
const [tools, setTools] = useState<MCPTool[]>([])
|
const [tools, setTools] = useState<MCPTool[]>([])
|
||||||
|
const [isShowRegistry, setIsShowRegistry] = useState(false)
|
||||||
useEffect(() => {
|
const [registry, setRegistry] = useState<Registry[]>()
|
||||||
if (server) {
|
|
||||||
form.setFieldsValue({
|
|
||||||
name: server.name,
|
|
||||||
description: server.description,
|
|
||||||
serverType: server.baseUrl ? 'sse' : 'stdio',
|
|
||||||
baseUrl: server.baseUrl || '',
|
|
||||||
command: server.command || '',
|
|
||||||
args: server.args ? server.args.join('\n') : '',
|
|
||||||
env: server.env
|
|
||||||
? Object.entries(server.env)
|
|
||||||
.map(([key, value]) => `${key}=${value}`)
|
|
||||||
.join('\n')
|
|
||||||
: '',
|
|
||||||
isActive: server.isActive
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [form, server])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const serverType = server.baseUrl ? 'sse' : 'stdio'
|
const serverType = server.baseUrl ? 'sse' : 'stdio'
|
||||||
setServerType(serverType)
|
setServerType(serverType)
|
||||||
|
|
||||||
|
// Set registry UI state based on command and registryUrl
|
||||||
|
if (server.command) {
|
||||||
|
handleCommandChange(server.command)
|
||||||
|
|
||||||
|
// If there's a registryUrl, ensure registry UI is shown
|
||||||
|
if (server.registryUrl) {
|
||||||
|
setIsShowRegistry(true)
|
||||||
|
|
||||||
|
// Determine registry type based on command
|
||||||
|
if (server.command.includes('uv') || server.command.includes('uvx')) {
|
||||||
|
setRegistry(PipRegistry)
|
||||||
|
} else if (
|
||||||
|
server.command.includes('npx') ||
|
||||||
|
server.command.includes('bun') ||
|
||||||
|
server.command.includes('bunx')
|
||||||
|
) {
|
||||||
|
setRegistry(NpmRegistry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
name: server.name,
|
name: server.name,
|
||||||
description: server.description,
|
description: server.description,
|
||||||
serverType: serverType,
|
serverType: serverType,
|
||||||
baseUrl: server.baseUrl || '',
|
baseUrl: server.baseUrl || '',
|
||||||
command: server.command || '',
|
command: server.command || '',
|
||||||
|
registryUrl: server.registryUrl || '',
|
||||||
|
isActive: server.isActive,
|
||||||
args: server.args ? server.args.join('\n') : '',
|
args: server.args ? server.args.join('\n') : '',
|
||||||
env: server.env
|
env: server.env
|
||||||
? Object.entries(server.env)
|
? Object.entries(server.env)
|
||||||
@ -72,7 +93,8 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
.join('\n')
|
.join('\n')
|
||||||
: ''
|
: ''
|
||||||
})
|
})
|
||||||
}, [form, server])
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [server])
|
||||||
|
|
||||||
// Watch the serverType field to update the form layout dynamically
|
// Watch the serverType field to update the form layout dynamically
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -110,32 +132,36 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
try {
|
try {
|
||||||
const values = await form.validateFields()
|
const values = await form.validateFields()
|
||||||
|
|
||||||
|
// set basic fields
|
||||||
const mcpServer: MCPServer = {
|
const mcpServer: MCPServer = {
|
||||||
id: server.id,
|
id: server.id,
|
||||||
name: values.name,
|
name: values.name,
|
||||||
description: values.description,
|
description: values.description,
|
||||||
isActive: values.isActive
|
isActive: values.isActive,
|
||||||
|
registryUrl: values.registryUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set stdio or sse server
|
||||||
if (values.serverType === 'sse') {
|
if (values.serverType === 'sse') {
|
||||||
mcpServer.baseUrl = values.baseUrl
|
mcpServer.baseUrl = values.baseUrl
|
||||||
} else {
|
} else {
|
||||||
mcpServer.command = values.command
|
mcpServer.command = values.command
|
||||||
mcpServer.args = values.args ? values.args.split('\n').filter((arg) => arg.trim() !== '') : []
|
mcpServer.args = values.args ? values.args.split('\n').filter((arg) => arg.trim() !== '') : []
|
||||||
|
}
|
||||||
|
|
||||||
|
// set env variables
|
||||||
|
if (values.env) {
|
||||||
const env: Record<string, string> = {}
|
const env: Record<string, string> = {}
|
||||||
if (values.env) {
|
values.env.split('\n').forEach((line) => {
|
||||||
values.env.split('\n').forEach((line) => {
|
if (line.trim()) {
|
||||||
if (line.trim()) {
|
const [key, ...chunks] = line.split('=')
|
||||||
const [key, ...chunks] = line.split('=')
|
const value = chunks.join('=')
|
||||||
const value = chunks.join('=')
|
if (key && value) {
|
||||||
if (key && value) {
|
env[key.trim()] = value.trim()
|
||||||
env[key.trim()] = value.trim()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
})
|
||||||
mcpServer.env = Object.keys(env).length > 0 ? env : undefined
|
mcpServer.env = env
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -156,9 +182,41 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
console.error('Failed to save MCP server settings:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watch for command field changes
|
||||||
|
const handleCommandChange = (command: string) => {
|
||||||
|
if (command.includes('uv') || command.includes('uvx')) {
|
||||||
|
setIsShowRegistry(true)
|
||||||
|
setRegistry(PipRegistry)
|
||||||
|
} else if (command.includes('npx') || command.includes('bun') || command.includes('bunx')) {
|
||||||
|
setIsShowRegistry(true)
|
||||||
|
setRegistry(NpmRegistry)
|
||||||
|
} else {
|
||||||
|
setIsShowRegistry(false)
|
||||||
|
setRegistry(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelectRegistry = (url: string) => {
|
||||||
|
const command = form.getFieldValue('command') || ''
|
||||||
|
|
||||||
|
// Add new registry env variables
|
||||||
|
if (command.includes('uv') || command.includes('uvx')) {
|
||||||
|
// envs['PIP_INDEX_URL'] = url
|
||||||
|
// envs['UV_DEFAULT_INDEX'] = url
|
||||||
|
form.setFieldsValue({ registryUrl: url })
|
||||||
|
} else if (command.includes('npx') || command.includes('bun') || command.includes('bunx')) {
|
||||||
|
// envs['NPM_CONFIG_REGISTRY'] = url
|
||||||
|
form.setFieldsValue({ registryUrl: url })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark form as changed
|
||||||
|
setIsFormChanged(true)
|
||||||
|
}
|
||||||
|
|
||||||
const onDeleteMcpServer = useCallback(
|
const onDeleteMcpServer = useCallback(
|
||||||
async (server: MCPServer) => {
|
async (server: MCPServer) => {
|
||||||
try {
|
try {
|
||||||
@ -281,9 +339,37 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
name="command"
|
name="command"
|
||||||
label={t('settings.mcp.command')}
|
label={t('settings.mcp.command')}
|
||||||
rules={[{ required: serverType === 'stdio', message: '' }]}>
|
rules={[{ required: serverType === 'stdio', message: '' }]}>
|
||||||
<Input placeholder="uvx or npx" />
|
<Input placeholder="uvx or npx" onChange={(e) => handleCommandChange(e.target.value)} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
{isShowRegistry && registry && (
|
||||||
|
<Form.Item
|
||||||
|
name="registryUrl"
|
||||||
|
label={t('settings.mcp.registry')}
|
||||||
|
tooltip={t('settings.mcp.registryTooltip')}>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio
|
||||||
|
key="no-proxy"
|
||||||
|
value=""
|
||||||
|
onChange={(e) => {
|
||||||
|
onSelectRegistry(e.target.value)
|
||||||
|
}}>
|
||||||
|
{t('settings.mcp.registryDefault')}
|
||||||
|
</Radio>
|
||||||
|
{registry.map((reg) => (
|
||||||
|
<Radio
|
||||||
|
key={reg.url}
|
||||||
|
value={reg.url}
|
||||||
|
onChange={(e) => {
|
||||||
|
onSelectRegistry(e.target.value)
|
||||||
|
}}>
|
||||||
|
{reg.name}
|
||||||
|
</Radio>
|
||||||
|
))}
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="args"
|
name="args"
|
||||||
label={t('settings.mcp.args')}
|
label={t('settings.mcp.args')}
|
||||||
|
|||||||
@ -368,6 +368,7 @@ export interface MCPServer {
|
|||||||
description?: string
|
description?: string
|
||||||
baseUrl?: string
|
baseUrl?: string
|
||||||
command?: string
|
command?: string
|
||||||
|
registryUrl?: string
|
||||||
args?: string[]
|
args?: string[]
|
||||||
env?: Record<string, string>
|
env?: Record<string, string>
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user