@lobehub/chat 1.81.9 → 1.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/ar/plugin.json +33 -2
  4. package/locales/bg-BG/plugin.json +33 -2
  5. package/locales/de-DE/plugin.json +33 -2
  6. package/locales/en-US/plugin.json +33 -2
  7. package/locales/es-ES/plugin.json +33 -2
  8. package/locales/fa-IR/plugin.json +33 -2
  9. package/locales/fr-FR/plugin.json +33 -2
  10. package/locales/it-IT/plugin.json +33 -2
  11. package/locales/ja-JP/plugin.json +33 -2
  12. package/locales/ko-KR/plugin.json +33 -2
  13. package/locales/nl-NL/plugin.json +33 -2
  14. package/locales/pl-PL/plugin.json +33 -2
  15. package/locales/pt-BR/plugin.json +33 -2
  16. package/locales/ru-RU/plugin.json +33 -2
  17. package/locales/tr-TR/plugin.json +33 -2
  18. package/locales/vi-VN/plugin.json +33 -2
  19. package/locales/zh-CN/plugin.json +33 -2
  20. package/locales/zh-TW/plugin.json +33 -2
  21. package/package.json +1 -1
  22. package/src/components/ManifestPreviewer/index.tsx +4 -1
  23. package/src/features/ChatInput/ActionBar/Tools/Dropdown.tsx +2 -1
  24. package/src/features/Conversation/Extras/Usage/index.tsx +7 -1
  25. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +1 -1
  26. package/src/features/PluginAvatar/index.tsx +2 -1
  27. package/src/features/PluginDevModal/MCPManifestForm.tsx +164 -0
  28. package/src/features/PluginDevModal/PluginPreview.tsx +4 -3
  29. package/src/features/PluginDevModal/index.tsx +43 -34
  30. package/src/features/PluginStore/AddPluginButton.tsx +3 -1
  31. package/src/features/PluginStore/PluginItem/Action.tsx +5 -2
  32. package/src/features/PluginStore/PluginItem/PluginAvatar.tsx +25 -0
  33. package/src/features/PluginStore/PluginItem/index.tsx +4 -3
  34. package/src/features/PluginTag/index.tsx +8 -2
  35. package/src/{server/modules/MCPClient → libs/mcp}/__tests__/index.test.ts +2 -2
  36. package/src/{server/modules/MCPClient/index.ts → libs/mcp/client.ts} +29 -33
  37. package/src/libs/mcp/index.ts +2 -0
  38. package/src/libs/mcp/types.ts +27 -0
  39. package/src/locales/default/plugin.ts +34 -3
  40. package/src/server/routers/tools/index.ts +2 -0
  41. package/src/server/routers/tools/mcp.ts +79 -0
  42. package/src/server/services/mcp/index.ts +157 -0
  43. package/src/services/mcp.ts +25 -0
  44. package/src/store/chat/slices/plugin/action.ts +46 -2
  45. package/src/types/tool/plugin.ts +9 -0
  46. /package/src/{server/modules/MCPClient → libs/mcp}/__tests__/__snapshots__/index.test.ts.snap +0 -0
@@ -34,9 +34,40 @@
34
34
  "desc": "Уникальный идентификатор плагина",
35
35
  "label": "Идентификатор"
36
36
  },
37
+ "mcp": {
38
+ "args": {
39
+ "desc": "Список параметров, передаваемых в команду STDIO",
40
+ "label": "Параметры команды",
41
+ "placeholder": "Например: --port 8080 --debug",
42
+ "tooltip": "Нажмите Enter после ввода параметров или используйте запятую/пробел для разделения"
43
+ },
44
+ "command": {
45
+ "desc": "Исполняемый файл или скрипт для запуска MCP STDIO плагина",
46
+ "label": "Команда",
47
+ "placeholder": "Например: python main.py или /path/to/executable"
48
+ },
49
+ "endpoint": {
50
+ "desc": "Введите адрес вашего MCP Streamable HTTP сервера",
51
+ "label": "MCP Endpoint URL"
52
+ },
53
+ "identifier": {
54
+ "desc": "Укажите имя для вашего MCP плагина, необходимо использовать английские символы",
55
+ "invalid": "Можно вводить только английские буквы, цифры, символы - и _",
56
+ "label": "Имя MCP плагина",
57
+ "placeholder": "Например: my-mcp-plugin"
58
+ },
59
+ "type": {
60
+ "desc": "Выберите способ связи для MCP плагина, веб-версия поддерживает только Streamable HTTP",
61
+ "label": "Тип MCP плагина"
62
+ },
63
+ "url": {
64
+ "desc": "Введите адрес Endpoint вашего MCP HTTP плагина",
65
+ "label": "HTTP Endpoint URL"
66
+ }
67
+ },
37
68
  "mode": {
38
- "local": "Локальная настройка",
39
- "local-tooltip": "Локальная настройка временно недоступна",
69
+ "mcp": "MCP плагин",
70
+ "mcpExp": "Экспериментальный",
40
71
  "url": "Ссылка онлайн"
41
72
  },
42
73
  "name": {
@@ -34,9 +34,40 @@
34
34
  "desc": "Eklenti için benzersiz tanımlayıcı",
35
35
  "label": "Tanımlayıcı"
36
36
  },
37
+ "mcp": {
38
+ "args": {
39
+ "desc": "STDIO komutuna iletilen parametreler listesi",
40
+ "label": "Komut Parametreleri",
41
+ "placeholder": "Örneğin: --port 8080 --debug",
42
+ "tooltip": "Parametreleri girdikten sonra enter tuşuna basın veya virgül/boşluk ile ayırın"
43
+ },
44
+ "command": {
45
+ "desc": "MCP STDIO eklentisini başlatmak için kullanılacak yürütülebilir dosya veya betik",
46
+ "label": "Komut",
47
+ "placeholder": "Örneğin: python main.py veya /path/to/executable"
48
+ },
49
+ "endpoint": {
50
+ "desc": "MCP Streamable HTTP Sunucunuzun adresini girin",
51
+ "label": "MCP Endpoint URL"
52
+ },
53
+ "identifier": {
54
+ "desc": "MCP eklentinize bir ad verin, İngilizce karakterler kullanmalısınız",
55
+ "invalid": "Sadece İngilizce karakterler, rakamlar, - ve _ bu iki sembolü girebilirsiniz",
56
+ "label": "MCP Eklenti Adı",
57
+ "placeholder": "Örneğin: my-mcp-plugin"
58
+ },
59
+ "type": {
60
+ "desc": "MCP eklentisinin iletişim yöntemini seçin, web sürümü yalnızca Streamable HTTP'yi destekler",
61
+ "label": "MCP Eklenti Türü"
62
+ },
63
+ "url": {
64
+ "desc": "MCP HTTP eklentinizin Endpoint adresini girin",
65
+ "label": "HTTP Endpoint URL"
66
+ }
67
+ },
37
68
  "mode": {
38
- "local": "Yapılandırma",
39
- "local-tooltip": "Yapılandırma geçici olarak desteklenmiyor",
69
+ "mcp": "MCP Eklentisi",
70
+ "mcpExp": "Deneysel",
40
71
  "url": "Çevrimiçi Bağlantı"
41
72
  },
42
73
  "name": {
@@ -35,8 +35,8 @@
35
35
  "label": "Định danh"
36
36
  },
37
37
  "mode": {
38
- "local": "Cấu hình trực quan",
39
- "local-tooltip": "Tạm thời không hỗ trợ cấu hình trực quan",
38
+ "mcp": "MCP Plugin",
39
+ "mcpExp": "Thí nghiệm",
40
40
  "url": "Liên kết trực tuyến"
41
41
  },
42
42
  "name": {
@@ -45,6 +45,37 @@
45
45
  "placeholder": "Tìm kiếm công cụ tìm kiếm"
46
46
  }
47
47
  },
48
+ "mcp": {
49
+ "args": {
50
+ "desc": "Danh sách các tham số được truyền cho lệnh STDIO",
51
+ "label": "Tham số lệnh",
52
+ "placeholder": "Ví dụ: --port 8080 --debug",
53
+ "tooltip": "Nhấn Enter sau khi nhập tham số hoặc sử dụng dấu phẩy/khoảng trắng để phân tách"
54
+ },
55
+ "command": {
56
+ "desc": "Tệp thực thi hoặc kịch bản để khởi động plugin MCP STDIO",
57
+ "label": "Lệnh",
58
+ "placeholder": "Ví dụ: python main.py hoặc /path/to/executable"
59
+ },
60
+ "endpoint": {
61
+ "desc": "Nhập địa chỉ của máy chủ HTTP Streamable MCP của bạn",
62
+ "label": "URL Điểm cuối MCP"
63
+ },
64
+ "identifier": {
65
+ "desc": "Chỉ định một tên cho plugin MCP của bạn, cần sử dụng ký tự tiếng Anh",
66
+ "invalid": "Chỉ có thể nhập ký tự tiếng Anh, số, và hai ký hiệu - và _",
67
+ "label": "Tên plugin MCP",
68
+ "placeholder": "Ví dụ: my-mcp-plugin"
69
+ },
70
+ "type": {
71
+ "desc": "Chọn phương thức giao tiếp của plugin MCP, phiên bản web chỉ hỗ trợ Streamable HTTP",
72
+ "label": "Loại plugin MCP"
73
+ },
74
+ "url": {
75
+ "desc": "Nhập địa chỉ Điểm cuối HTTP của plugin MCP của bạn",
76
+ "label": "URL Điểm cuối HTTP"
77
+ }
78
+ },
48
79
  "meta": {
49
80
  "author": {
50
81
  "desc": "Tác giả của plugin",
@@ -35,8 +35,8 @@
35
35
  "label": "标识符"
36
36
  },
37
37
  "mode": {
38
- "local": "可视化配置",
39
- "local-tooltip": "暂时不支持可视化配置",
38
+ "mcp": "MCP 插件",
39
+ "mcpExp": "实验性",
40
40
  "url": "在线链接"
41
41
  },
42
42
  "name": {
@@ -45,6 +45,37 @@
45
45
  "placeholder": "搜索引擎"
46
46
  }
47
47
  },
48
+ "mcp": {
49
+ "args": {
50
+ "desc": "传递给 STDIO 命令的参数列表",
51
+ "label": "命令参数",
52
+ "placeholder": "例如:--port 8080 --debug",
53
+ "tooltip": "输入参数后按回车或使用逗号/空格分隔"
54
+ },
55
+ "command": {
56
+ "desc": "用于启动 MCP STDIO 插件的可执行文件或脚本",
57
+ "label": "命令",
58
+ "placeholder": "例如:python main.py 或 /path/to/executable"
59
+ },
60
+ "endpoint": {
61
+ "desc": "输入你的 MCP Streamable HTTP Server 的地址",
62
+ "label": "MCP Endpoint URL"
63
+ },
64
+ "identifier": {
65
+ "desc": "为你的 MCP 插件指定一个名称,需要使用英文字符",
66
+ "invalid": "只能输入英文字符、数字 、- 和_ 这两个符号",
67
+ "label": "MCP 插件名称",
68
+ "placeholder": "例如:my-mcp-plugin"
69
+ },
70
+ "type": {
71
+ "desc": "选择 MCP 插件的通信方式,网页版只支持 Streamable HTTP",
72
+ "label": "MCP 插件类型"
73
+ },
74
+ "url": {
75
+ "desc": "输入你的 MCP HTTP 插件的 Endpoint 地址",
76
+ "label": "HTTP Endpoint URL"
77
+ }
78
+ },
48
79
  "meta": {
49
80
  "author": {
50
81
  "desc": "插件的作者",
@@ -34,9 +34,40 @@
34
34
  "desc": "外掛的唯一識別碼",
35
35
  "label": "識別碼"
36
36
  },
37
+ "mcp": {
38
+ "args": {
39
+ "desc": "傳遞給 STDIO 命令的參數列表",
40
+ "label": "命令參數",
41
+ "placeholder": "例如:--port 8080 --debug",
42
+ "tooltip": "輸入參數後按回車或使用逗號/空格分隔"
43
+ },
44
+ "command": {
45
+ "desc": "用於啟動 MCP STDIO 插件的可執行文件或腳本",
46
+ "label": "命令",
47
+ "placeholder": "例如:python main.py 或 /path/to/executable"
48
+ },
49
+ "endpoint": {
50
+ "desc": "輸入你的 MCP Streamable HTTP Server 的地址",
51
+ "label": "MCP Endpoint URL"
52
+ },
53
+ "identifier": {
54
+ "desc": "為你的 MCP 插件指定一個名稱,需要使用英文字符",
55
+ "invalid": "只能輸入英文字符、數字、- 和_ 這兩個符號",
56
+ "label": "MCP 插件名稱",
57
+ "placeholder": "例如:my-mcp-plugin"
58
+ },
59
+ "type": {
60
+ "desc": "選擇 MCP 插件的通信方式,網頁版只支持 Streamable HTTP",
61
+ "label": "MCP 插件類型"
62
+ },
63
+ "url": {
64
+ "desc": "輸入你的 MCP HTTP 插件的 Endpoint 地址",
65
+ "label": "HTTP Endpoint URL"
66
+ }
67
+ },
37
68
  "mode": {
38
- "local": "視覺設定",
39
- "local-tooltip": "目前不支援視覺設定",
69
+ "mcp": "MCP 插件",
70
+ "mcpExp": "實驗性",
40
71
  "url": "線上連結"
41
72
  },
42
73
  "name": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.81.9",
3
+ "version": "1.82.0",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -13,7 +13,10 @@ const ManifestPreviewer = memo<PluginManifestPreviewerProps>(
13
13
  <Popover
14
14
  arrow={false}
15
15
  content={
16
- <Highlighter language={'json'} style={{ maxHeight: 600, maxWidth: 400 }}>
16
+ <Highlighter
17
+ language={'json'}
18
+ style={{ maxHeight: 600, maxWidth: 400, overflow: 'scroll' }}
19
+ >
17
20
  {JSON.stringify(manifest, null, 2)}
18
21
  </Highlighter>
19
22
  }
@@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next';
9
9
  import { Flexbox } from 'react-layout-kit';
10
10
 
11
11
  import PluginStore from '@/features/PluginStore';
12
+ import PluginAvatar from '@/features/PluginStore/PluginItem/PluginAvatar';
12
13
  import { useCheckPluginsIsInstalled } from '@/hooks/useCheckPluginsIsInstalled';
13
14
  import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
14
15
  import { useWorkspaceModal } from '@/hooks/useWorkspaceModal';
@@ -67,7 +68,7 @@ const DropdownMenu = memo<PropsWithChildren>(({ children }) => {
67
68
  children: [
68
69
  ...list.map((item) => ({
69
70
  icon: item.meta?.avatar ? (
70
- <Avatar avatar={pluginHelpers.getPluginAvatar(item.meta)} size={24} />
71
+ <PluginAvatar avatar={pluginHelpers.getPluginAvatar(item.meta)} size={24} />
71
72
  ) : (
72
73
  <Icon icon={ToyBrick} size={{ fontSize: 16 }} style={{ padding: 4 }} />
73
74
  ),
@@ -24,7 +24,13 @@ const Usage = memo<UsageProps>(({ model, metadata, provider }) => {
24
24
  const { styles } = useStyles();
25
25
 
26
26
  return (
27
- <Flexbox align={'center'} className={styles.container} horizontal justify={'space-between'}>
27
+ <Flexbox
28
+ align={'center'}
29
+ className={styles.container}
30
+ gap={12}
31
+ horizontal
32
+ justify={'space-between'}
33
+ >
28
34
  <Center gap={4} horizontal style={{ fontSize: 12 }}>
29
35
  <ModelIcon model={model as string} type={'mono'} />
30
36
  {model}
@@ -75,7 +75,7 @@ const CustomRender = memo<CustomRenderProps>(
75
75
  useEffect(() => {
76
76
  if (!plugin?.type || loading) return;
77
77
 
78
- setShowPluginRender(plugin?.type !== 'default');
78
+ setShowPluginRender(!['default', 'mcp'].includes(plugin?.type));
79
79
  }, [plugin?.type, loading]);
80
80
 
81
81
  if (loading) return <Arguments arguments={requestArgs} shine />;
@@ -1,9 +1,10 @@
1
- import { Avatar, Icon } from '@lobehub/ui';
1
+ import { Icon } from '@lobehub/ui';
2
2
  import isEqual from 'fast-deep-equal';
3
3
  import { LucideToyBrick } from 'lucide-react';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
+ import Avatar from '@/features/PluginStore/PluginItem/PluginAvatar';
7
8
  import { pluginHelpers, useToolStore } from '@/store/tool';
8
9
  import { toolSelectors } from '@/store/tool/selectors';
9
10
 
@@ -0,0 +1,164 @@
1
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
+ import { ActionIcon, FormItem } from '@lobehub/ui';
3
+ import { Form, FormInstance, Input, Radio, Select } from 'antd';
4
+ import { FileCode, RotateCwIcon } from 'lucide-react';
5
+ import { useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import ManifestPreviewer from '@/components/ManifestPreviewer';
10
+ import { isDesktop } from '@/const/version';
11
+ import { mcpService } from '@/services/mcp';
12
+ import { useToolStore } from '@/store/tool';
13
+ import { pluginSelectors } from '@/store/tool/selectors';
14
+ import { PluginInstallError } from '@/types/tool/plugin';
15
+
16
+ interface MCPManifestFormProps {
17
+ form: FormInstance;
18
+ isEditMode?: boolean;
19
+ }
20
+
21
+ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
22
+ const { t } = useTranslation('plugin');
23
+ const mcpType = Form.useWatch(['customParams', 'mcp', 'type'], form);
24
+ const [manifest, setManifest] = useState<LobeChatPluginManifest>();
25
+ const pluginIds = useToolStore(pluginSelectors.storeAndInstallPluginsIdList);
26
+
27
+ const HTTP_URL_KEY = ['customParams', 'mcp', 'url'];
28
+ return (
29
+ <Form form={form} layout={'vertical'}>
30
+ <Flexbox gap={16}>
31
+ <Form.Item
32
+ extra={t('dev.mcp.identifier.desc')}
33
+ label={t('dev.mcp.identifier.label')}
34
+ name={'identifier'}
35
+ rules={[
36
+ { required: true },
37
+ {
38
+ message: t('dev.mcp.identifier.invalid'),
39
+ pattern: /^[\w-]+$/,
40
+ },
41
+ // 编辑模式下,不进行重复校验
42
+ isEditMode
43
+ ? {}
44
+ : {
45
+ message: t('dev.meta.identifier.errorDuplicate'),
46
+ validator: async () => {
47
+ const id = form.getFieldValue('identifier');
48
+ if (!id) return true;
49
+
50
+ if (pluginIds.includes(id)) {
51
+ throw new Error('Duplicate');
52
+ }
53
+ },
54
+ },
55
+ ]}
56
+ >
57
+ <Input placeholder={t('dev.mcp.identifier.placeholder')} />
58
+ </Form.Item>
59
+
60
+ <Form.Item
61
+ extra={t('dev.mcp.type.desc')}
62
+ initialValue={'http'}
63
+ label={t('dev.mcp.type.label')}
64
+ name={['customParams', 'mcp', 'type']}
65
+ rules={[{ required: true }]}
66
+ >
67
+ <Radio.Group>
68
+ <Radio value={'http'}>Streamable HTTP</Radio>
69
+ <Radio disabled={!isDesktop} value={'stdio'}>
70
+ STDIO
71
+ </Radio>
72
+ </Radio.Group>
73
+ </Form.Item>
74
+
75
+ {mcpType === 'http' && (
76
+ <Form.Item
77
+ extra={
78
+ <Flexbox horizontal justify={'space-between'} style={{ marginTop: 8 }}>
79
+ {t('dev.mcp.url.desc')}
80
+ {manifest && (
81
+ <ManifestPreviewer manifest={manifest}>
82
+ <ActionIcon
83
+ icon={FileCode}
84
+ size={'small'}
85
+ title={t('dev.meta.manifest.preview')}
86
+ />
87
+ </ManifestPreviewer>
88
+ )}
89
+ </Flexbox>
90
+ }
91
+ hasFeedback
92
+ label={t('dev.mcp.url.label')}
93
+ name={HTTP_URL_KEY}
94
+ rules={[
95
+ { required: true },
96
+ { type: 'url' },
97
+ {
98
+ validator: async (_, value) => {
99
+ if (!value) return true;
100
+
101
+ try {
102
+ const data = await mcpService.getStreamableMcpServerManifest(
103
+ form.getFieldValue('identifier'),
104
+ value,
105
+ );
106
+ setManifest(data);
107
+
108
+ form.setFieldsValue({ identifier: data.identifier, manifest: data });
109
+ } catch (error) {
110
+ const err = error as PluginInstallError;
111
+ throw t(`error.${err.message}`, { error: err.cause! });
112
+ }
113
+ },
114
+ },
115
+ ]}
116
+ >
117
+ <Input
118
+ placeholder="https://mcp.higress.ai/mcp-github/xxxxx"
119
+ suffix={
120
+ <ActionIcon
121
+ icon={RotateCwIcon}
122
+ onClick={(e) => {
123
+ e.stopPropagation();
124
+ form.validateFields([HTTP_URL_KEY]);
125
+ }}
126
+ size={'small'}
127
+ title={t('dev.meta.manifest.refresh')}
128
+ />
129
+ }
130
+ />
131
+ </Form.Item>
132
+ )}
133
+
134
+ {mcpType === 'stdio' && (
135
+ <>
136
+ <Form.Item
137
+ extra={t('dev.mcp.command.desc')}
138
+ label={t('dev.mcp.command.label')}
139
+ name={['mcp', 'command']}
140
+ rules={[{ required: true }]}
141
+ >
142
+ <Input placeholder={t('dev.mcp.command.placeholder')} />
143
+ </Form.Item>
144
+ <Form.Item
145
+ extra={t('dev.mcp.args.desc')}
146
+ label={t('dev.mcp.args.label')}
147
+ name={['mcp', 'args']}
148
+ tooltip={t('dev.mcp.args.tooltip')}
149
+ >
150
+ <Select
151
+ mode="tags"
152
+ placeholder={t('dev.mcp.args.placeholder')}
153
+ tokenSeparators={[',', ' ']}
154
+ />
155
+ </Form.Item>
156
+ </>
157
+ )}
158
+ <FormItem name={'manifest'} noStyle />
159
+ </Flexbox>
160
+ </Form>
161
+ );
162
+ };
163
+
164
+ export default MCPManifestForm;
@@ -1,9 +1,10 @@
1
- import { Avatar, Form } from '@lobehub/ui';
1
+ import { Form } from '@lobehub/ui';
2
2
  import { Form as AForm, Card, FormInstance } from 'antd';
3
3
  import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
 
7
+ import PluginAvatar from '@/features/PluginStore/PluginItem/PluginAvatar';
7
8
  import PluginTag from '@/features/PluginStore/PluginItem/PluginTag';
8
9
  import { pluginHelpers } from '@/store/tool';
9
10
  import { LobeToolCustomPlugin } from '@/types/tool/plugin';
@@ -15,7 +16,7 @@ const PluginPreview = memo<{ form: FormInstance }>(({ form }) => {
15
16
  const meta = plugin?.manifest?.meta;
16
17
 
17
18
  const items = {
18
- avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta)} style={{ flex: 'none' }} />,
19
+ avatar: <PluginAvatar avatar={pluginHelpers.getPluginAvatar(meta)} />,
19
20
  desc: pluginHelpers.getPluginDesc(meta) || 'Plugin Description',
20
21
  label: (
21
22
  <Flexbox align={'center'} gap={8} horizontal>
@@ -27,7 +28,7 @@ const PluginPreview = memo<{ form: FormInstance }>(({ form }) => {
27
28
  };
28
29
 
29
30
  return (
30
- <Card bodyStyle={{ padding: '0 16px' }} size={'small'} title={t('dev.preview.card')}>
31
+ <Card size={'small'} styles={{ body: { padding: '0 16px' } }} title={t('dev.preview.card')}>
31
32
  <Form.Item {...items} colon={false} style={{ alignItems: 'center', marginBottom: 0 }} />
32
33
  </Card>
33
34
  );
@@ -1,5 +1,5 @@
1
- import { Alert, Icon, Modal, Tooltip } from '@lobehub/ui';
2
- import { App, Button, Form, Popconfirm, Segmented } from 'antd';
1
+ import { Alert, Icon, Modal } from '@lobehub/ui';
2
+ import { App, Button, Form, Popconfirm, Segmented, Tag } from 'antd';
3
3
  import { useResponsive } from 'antd-style';
4
4
  import { MoveUpRight } from 'lucide-react';
5
5
  import { memo, useEffect, useState } from 'react';
@@ -9,6 +9,7 @@ import { Flexbox } from 'react-layout-kit';
9
9
  import { WIKI_PLUGIN_GUIDE } from '@/const/url';
10
10
  import { LobeToolCustomPlugin } from '@/types/tool/plugin';
11
11
 
12
+ import MCPManifestForm from './MCPManifestForm';
12
13
  import PluginPreview from './PluginPreview';
13
14
  import UrlManifestForm from './UrlManifestForm';
14
15
 
@@ -25,7 +26,7 @@ interface DevModalProps {
25
26
  const DevModal = memo<DevModalProps>(
26
27
  ({ open, mode = 'create', value, onValueChange, onSave, onOpenChange, onDelete }) => {
27
28
  const isEditMode = mode === 'edit';
28
- const [configMode, setConfigMode] = useState<'url' | 'local'>('url');
29
+ const [configMode, setConfigMode] = useState<'url' | 'mcp'>('mcp');
29
30
  const { t } = useTranslation('plugin');
30
31
  const { message } = App.useApp();
31
32
  const [submitting, setSubmitting] = useState(false);
@@ -118,49 +119,57 @@ const DevModal = memo<DevModalProps>(
118
119
  e.stopPropagation();
119
120
  }}
120
121
  >
121
- <Alert
122
- message={
123
- <Trans i18nKey={'dev.modalDesc'} ns={'plugin'}>
124
- 添加自定义插件后,可用于插件开发验证,也可直接在会话中使用。插件开发文档请参考:
125
- <a
126
- href={WIKI_PLUGIN_GUIDE}
127
- rel="noreferrer"
128
- style={{ paddingInline: 8 }}
129
- target={'_blank'}
130
- >
131
- 文档
132
- </a>
133
- <Icon icon={MoveUpRight} />
134
- </Trans>
135
- }
136
- showIcon
137
- type={'info'}
138
- />
139
122
  <Segmented
140
123
  block
141
124
  onChange={(e) => {
142
- setConfigMode(e as any);
125
+ setConfigMode(e as 'url' | 'mcp');
143
126
  }}
144
127
  options={[
145
128
  {
146
- label: t('dev.manifest.mode.url'),
147
- value: 'url',
148
- },
149
- {
150
- disabled: true,
151
129
  label: (
152
- <Tooltip title={t('dev.manifest.mode.local-tooltip')}>
153
- {t('dev.manifest.mode.local')}
154
- </Tooltip>
130
+ <Flexbox align={'center'} gap={4} horizontal justify={'center'}>
131
+ {t('dev.manifest.mode.mcp')}
132
+ <div>
133
+ <Tag bordered={false} color={'warning'}>
134
+ {t('dev.manifest.mode.mcpExp')}
135
+ </Tag>
136
+ </div>
137
+ </Flexbox>
155
138
  ),
156
- value: 'local',
139
+ value: 'mcp',
140
+ },
141
+ {
142
+ label: t('dev.manifest.mode.url'),
143
+ value: 'url',
157
144
  },
158
145
  ]}
146
+ value={configMode}
159
147
  />
160
148
 
161
- {configMode === 'url' ? (
162
- <UrlManifestForm form={form} isEditMode={mode === 'edit'} />
163
- ) : null}
149
+ {configMode === 'url' && (
150
+ <>
151
+ <Alert
152
+ message={
153
+ <Trans i18nKey={'dev.modalDesc'} ns={'plugin'}>
154
+ 添加自定义插件后,可用于插件开发验证,也可直接在会话中使用。插件开发文档请参考:
155
+ <a
156
+ href={WIKI_PLUGIN_GUIDE}
157
+ rel="noreferrer"
158
+ style={{ paddingInline: 8 }}
159
+ target={'_blank'}
160
+ >
161
+ 文档
162
+ </a>
163
+ <Icon icon={MoveUpRight} />
164
+ </Trans>
165
+ }
166
+ showIcon
167
+ type={'info'}
168
+ />
169
+ <UrlManifestForm form={form} isEditMode={mode === 'edit'} />
170
+ </>
171
+ )}
172
+ {configMode === 'mcp' && <MCPManifestForm form={form} />}
164
173
  <PluginPreview form={form} />
165
174
  </Flexbox>
166
175
  </Modal>
@@ -5,6 +5,7 @@ import { forwardRef, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import DevModal from '@/features/PluginDevModal';
8
+ import { useAgentStore } from '@/store/agent';
8
9
  import { useToolStore } from '@/store/tool';
9
10
 
10
11
  const AddPluginButton = forwardRef<HTMLButtonElement>((props, ref) => {
@@ -15,6 +16,7 @@ const AddPluginButton = forwardRef<HTMLButtonElement>((props, ref) => {
15
16
  s.installCustomPlugin,
16
17
  s.updateNewCustomPlugin,
17
18
  ]);
19
+ const togglePlugin = useAgentStore((s) => s.togglePlugin);
18
20
 
19
21
  return (
20
22
  <div
@@ -26,7 +28,7 @@ const AddPluginButton = forwardRef<HTMLButtonElement>((props, ref) => {
26
28
  onOpenChange={setModal}
27
29
  onSave={async (devPlugin) => {
28
30
  await installCustomPlugin(devPlugin);
29
- // toggleAgentPlugin(devPlugin.identifier);
31
+ await togglePlugin(devPlugin.identifier);
30
32
  }}
31
33
  onValueChange={updateNewDevPlugin}
32
34
  open={showModal}