feat: mcp npx list (#3409)
* feat: add npm scope search functionality in MCPSettings - Integrated npx-scope-finder to enable searching for npm packages by scope. - Added UI elements for inputting npm scope and displaying search results in a table format. - Enhanced user feedback with loading indicators and messages for search results. * feat: add key property to package formatting in MCPSettings - Added a key property to the package formatting logic to ensure unique identification of each package in the results. * feat: enhance MCPSettings with NPX package list localization - Added localization support for NPX package list in English, Japanese, Russian, Simplified Chinese, and Traditional Chinese. - Updated UI elements in MCPSettings to utilize localized strings for package list features, including title, description, and various labels. - Improved user experience by integrating translations for package-related actions and placeholders.
This commit is contained in:
parent
45c10fa166
commit
e0e1d285e4
@ -83,6 +83,7 @@
|
||||
"fetch-socks": "^1.3.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"npx-scope-finder": "^1.2.0",
|
||||
"officeparser": "^4.1.1",
|
||||
"p-queue": "^8.1.0",
|
||||
"tokenx": "^0.4.1",
|
||||
|
||||
@ -824,7 +824,19 @@
|
||||
"updateSuccess": "Server updated successfully",
|
||||
"updateError": "Failed to update server",
|
||||
"url": "URL",
|
||||
"toggleError": "Toggle failed"
|
||||
"toggleError": "Toggle failed",
|
||||
"npx_list": {
|
||||
"title": "NPX Package List",
|
||||
"desc": "Search and add npm packages as MCP servers",
|
||||
"scope_placeholder": "Enter npm scope (e.g. @your-org)",
|
||||
"search": "Search",
|
||||
"package_name": "Package Name",
|
||||
"description": "Description",
|
||||
"usage": "Usage",
|
||||
"npm": "NPM",
|
||||
"version": "Version",
|
||||
"actions": "Actions"
|
||||
}
|
||||
},
|
||||
"messages.divider": "Show divider between messages",
|
||||
"messages.grid_columns": "Message grid display columns",
|
||||
|
||||
@ -824,7 +824,19 @@
|
||||
"updateSuccess": "サーバーが正常に更新されました",
|
||||
"updateError": "サーバーの更新に失敗しました",
|
||||
"url": "URL",
|
||||
"toggleError": "切り替えに失敗しました"
|
||||
"toggleError": "切り替えに失敗しました",
|
||||
"npx_list": {
|
||||
"title": "NPX パッケージリスト",
|
||||
"desc": "npm パッケージを検索して MCP サーバーとして追加",
|
||||
"scope_placeholder": "npm スコープを入力 (例: @your-org)",
|
||||
"search": "検索",
|
||||
"package_name": "パッケージ名",
|
||||
"description": "説明",
|
||||
"usage": "使用法",
|
||||
"npm": "NPM",
|
||||
"version": "バージョン",
|
||||
"actions": "アクション"
|
||||
}
|
||||
},
|
||||
"messages.divider": "メッセージ間に区切り線を表示",
|
||||
"messages.grid_columns": "メッセージグリッドの表示列数",
|
||||
|
||||
@ -824,7 +824,19 @@
|
||||
"updateSuccess": "Сервер успешно обновлен",
|
||||
"updateError": "Ошибка обновления сервера",
|
||||
"url": "URL",
|
||||
"toggleError": "Переключение не удалось"
|
||||
"toggleError": "Переключение не удалось",
|
||||
"npx_list": {
|
||||
"title": "Список пакетов NPX",
|
||||
"desc": "Поиск и добавление npm пакетов в качестве MCP серверов",
|
||||
"scope_placeholder": "Введите область npm (например, @your-org)",
|
||||
"search": "Поиск",
|
||||
"package_name": "Имя пакета",
|
||||
"description": "Описание",
|
||||
"usage": "Использование",
|
||||
"npm": "NPM",
|
||||
"version": "Версия",
|
||||
"actions": "Действия"
|
||||
}
|
||||
},
|
||||
"messages.divider": "Показывать разделитель между сообщениями",
|
||||
"messages.grid_columns": "Количество столбцов сетки сообщений",
|
||||
|
||||
@ -824,7 +824,19 @@
|
||||
"updateSuccess": "服务器更新成功",
|
||||
"updateError": "更新服务器失败",
|
||||
"url": "URL",
|
||||
"toggleError": "切换失败"
|
||||
"toggleError": "切换失败",
|
||||
"npx_list": {
|
||||
"title": "NPX 包列表",
|
||||
"desc": "搜索并添加 npm 包作为 MCP 服务",
|
||||
"scope_placeholder": "输入 npm 作用域 (例如 @your-org)",
|
||||
"search": "搜索",
|
||||
"package_name": "包名称",
|
||||
"description": "描述",
|
||||
"usage": "用法",
|
||||
"npm": "NPM",
|
||||
"version": "版本",
|
||||
"actions": "操作"
|
||||
}
|
||||
},
|
||||
"messages.divider": "消息分割线",
|
||||
"messages.grid_columns": "消息网格展示列数",
|
||||
|
||||
@ -824,7 +824,19 @@
|
||||
"updateSuccess": "伺服器更新成功",
|
||||
"updateError": "更新伺服器失敗",
|
||||
"url": "URL",
|
||||
"toggleError": "切換失敗"
|
||||
"toggleError": "切換失敗",
|
||||
"npx_list": {
|
||||
"title": "NPX 包列表",
|
||||
"desc": "搜索並添加 npm 包作為 MCP 服務",
|
||||
"scope_placeholder": "輸入 npm 作用域 (例如 @your-org)",
|
||||
"search": "搜索",
|
||||
"package_name": "包名稱",
|
||||
"description": "描述",
|
||||
"usage": "用法",
|
||||
"npm": "NPM",
|
||||
"version": "版本",
|
||||
"actions": "操作"
|
||||
}
|
||||
},
|
||||
"messages.divider": "訊息間顯示分隔線",
|
||||
"messages.grid_columns": "訊息網格展示列數",
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined, QuestionCircleOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { MCPServer } from '@renderer/types'
|
||||
import { Button, Card, Form, Input, Modal, Radio, Space, Switch, Table, Tag, Tooltip, Typography } from 'antd'
|
||||
import { Button, Card, Form, Input, Modal, Radio, Space, Spin, Switch, Table, Tag, Tooltip, Typography } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { npxFinder } from 'npx-scope-finder'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@ -20,6 +21,15 @@ interface MCPFormValues {
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
interface SearchResult {
|
||||
name: string
|
||||
description: string
|
||||
version: string
|
||||
usage: string
|
||||
npmLink: string
|
||||
fullName: string
|
||||
}
|
||||
|
||||
const MCPSettings: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
@ -32,6 +42,48 @@ const MCPSettings: FC = () => {
|
||||
const [form] = Form.useForm<MCPFormValues>()
|
||||
const [serverType, setServerType] = useState<'sse' | 'stdio'>('stdio')
|
||||
|
||||
// Add new state variables for npm scope search
|
||||
const [npmScope, setNpmScope] = useState('')
|
||||
const [searchLoading, setSearchLoading] = useState(false)
|
||||
const [searchResults, setSearchResults] = useState<SearchResult[]>([])
|
||||
|
||||
// Add new function to handle npm scope search
|
||||
const handleNpmSearch = async () => {
|
||||
if (!npmScope.trim()) {
|
||||
window.message.warning('Please enter an npm scope')
|
||||
return
|
||||
}
|
||||
|
||||
setSearchLoading(true)
|
||||
try {
|
||||
// Call npxFinder to search for packages
|
||||
const packages = await npxFinder(npmScope)
|
||||
|
||||
// Map the packages to our desired format
|
||||
const formattedResults = packages.map((pkg) => {
|
||||
return {
|
||||
key: pkg.name,
|
||||
name: pkg.name || '',
|
||||
description: pkg.description || 'No description available',
|
||||
version: pkg.version || 'Latest',
|
||||
usage: `npx ${pkg.name}`,
|
||||
npmLink: pkg.links?.npm || `https://www.npmjs.com/package/${pkg.name}`,
|
||||
fullName: pkg.name || ''
|
||||
}
|
||||
})
|
||||
|
||||
setSearchResults(formattedResults)
|
||||
|
||||
if (formattedResults.length === 0) {
|
||||
window.message.info('No packages found for this scope')
|
||||
}
|
||||
} catch (error: any) {
|
||||
window.message.error(`Failed to search npm packages: ${error.message}`)
|
||||
} finally {
|
||||
setSearchLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Watch the serverType field to update the form layout dynamically
|
||||
useEffect(() => {
|
||||
const type = form.getFieldValue('serverType')
|
||||
@ -44,7 +96,6 @@ const MCPSettings: FC = () => {
|
||||
form.resetFields()
|
||||
form.setFieldsValue({ serverType: 'stdio', isActive: true })
|
||||
setServerType('stdio')
|
||||
setEditingServer(null)
|
||||
setIsModalVisible(true)
|
||||
}
|
||||
|
||||
@ -284,6 +335,96 @@ const MCPSettings: FC = () => {
|
||||
})}
|
||||
/>
|
||||
</Card>
|
||||
</SettingGroup>
|
||||
|
||||
<SettingGroup theme={theme}>
|
||||
<SettingTitle>{t('settings.mcp.npx_list.title')}</SettingTitle>
|
||||
<SettingDivider />
|
||||
<Paragraph type="secondary" style={{ margin: '0 0 20px 0' }}>
|
||||
{t('settings.mcp.npx_list.desc')}
|
||||
</Paragraph>
|
||||
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Input
|
||||
placeholder={t('settings.mcp.npx_list.scope_placeholder')}
|
||||
value={npmScope}
|
||||
onChange={(e) => setNpmScope(e.target.value)}
|
||||
onPressEnter={handleNpmSearch}
|
||||
/>
|
||||
<Button type="primary" icon={<SearchOutlined />} onClick={handleNpmSearch} disabled={searchLoading}>
|
||||
{t('settings.mcp.npx_list.search')}
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
|
||||
{searchLoading ? (
|
||||
<div style={{ textAlign: 'center', padding: '20px' }}>
|
||||
<Spin />
|
||||
</div>
|
||||
) : searchResults.length > 0 ? (
|
||||
<Table<SearchResult>
|
||||
dataSource={searchResults}
|
||||
columns={[
|
||||
{
|
||||
title: t('settings.mcp.npx_list.package_name'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '200px'
|
||||
},
|
||||
{
|
||||
title: t('settings.mcp.npx_list.description'),
|
||||
key: 'description',
|
||||
render: (_, record: SearchResult) => (
|
||||
<Space direction="vertical" size="small">
|
||||
<Text>{record.description}</Text>
|
||||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||||
{t('settings.mcp.npx_list.usage')}: {record.usage}
|
||||
</Text>
|
||||
<a href={record.npmLink} target="_blank" rel="noopener noreferrer" style={{ fontSize: '12px' }}>
|
||||
{record.npmLink}
|
||||
</a>
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t('settings.mcp.npx_list.version'),
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
width: '100px'
|
||||
},
|
||||
{
|
||||
title: t('settings.mcp.npx_list.actions'),
|
||||
key: 'actions',
|
||||
width: '100px',
|
||||
render: (_, record: SearchResult) => (
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
// 创建一个临时的 MCP 服务器对象
|
||||
const tempServer: MCPServer = {
|
||||
name: record.name,
|
||||
description: `${record.description}\n\n${t('settings.mcp.npx_list.usage')}: ${record.usage}\n${t('settings.mcp.npx_list.npm')}: ${record.npmLink}`,
|
||||
command: 'npx',
|
||||
args: ['-y', record.fullName],
|
||||
isActive: true
|
||||
}
|
||||
|
||||
// 使用 showEditModal 函数设置表单值并显示弹窗
|
||||
showEditModal(tempServer)
|
||||
}}>
|
||||
{t('settings.mcp.addServer')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
]}
|
||||
pagination={false}
|
||||
size="small"
|
||||
bordered
|
||||
/>
|
||||
) : null}
|
||||
</Space>
|
||||
</SettingGroup>
|
||||
|
||||
<Modal
|
||||
title={editingServer ? t('settings.mcp.editServer') : t('settings.mcp.addServer')}
|
||||
@ -305,11 +446,7 @@ const MCPSettings: FC = () => {
|
||||
<TextArea rows={2} placeholder={t('common.description')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="serverType"
|
||||
label={t('settings.mcp.type')}
|
||||
rules={[{ required: true }]}
|
||||
initialValue="stdio">
|
||||
<Form.Item name="serverType" label={t('settings.mcp.type')} rules={[{ required: true }]} initialValue="stdio">
|
||||
<Radio.Group
|
||||
onChange={(e) => setServerType(e.target.value)}
|
||||
options={[
|
||||
@ -353,7 +490,6 @@ const MCPSettings: FC = () => {
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</SettingGroup>
|
||||
</SettingContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@ -3235,6 +3235,7 @@ __metadata:
|
||||
lodash: "npm:^4.17.21"
|
||||
markdown-it: "npm:^14.1.0"
|
||||
mime: "npm:^4.0.4"
|
||||
npx-scope-finder: "npm:^1.2.0"
|
||||
officeparser: "npm:^4.1.1"
|
||||
openai: "patch:openai@npm%3A4.77.3#~/.yarn/patches/openai-npm-4.77.3-59c6d42e7a.patch"
|
||||
p-queue: "npm:^8.1.0"
|
||||
@ -11173,6 +11174,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"npx-scope-finder@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "npx-scope-finder@npm:1.2.0"
|
||||
checksum: 10c0/2e397317cfd0bc28de9255a774a4872f89586ae3cdd86a88c3f0c02e4ad834a5750b299d3c14b3c825978f807dff07c0c764e3409f2163dfcaf32ff97696e452
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"number-is-nan@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "number-is-nan@npm:1.0.1"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user