@lobehub/chat 1.84.4 → 1.84.6

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 (53) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/error.json +4 -1
  4. package/locales/ar/plugin.json +8 -0
  5. package/locales/bg-BG/error.json +4 -1
  6. package/locales/bg-BG/plugin.json +8 -0
  7. package/locales/de-DE/error.json +4 -1
  8. package/locales/de-DE/plugin.json +8 -0
  9. package/locales/en-US/error.json +4 -1
  10. package/locales/en-US/plugin.json +8 -0
  11. package/locales/es-ES/error.json +4 -1
  12. package/locales/es-ES/plugin.json +8 -0
  13. package/locales/fa-IR/error.json +4 -1
  14. package/locales/fa-IR/plugin.json +8 -0
  15. package/locales/fr-FR/error.json +4 -1
  16. package/locales/fr-FR/plugin.json +8 -0
  17. package/locales/it-IT/error.json +4 -1
  18. package/locales/it-IT/plugin.json +8 -0
  19. package/locales/ja-JP/error.json +4 -1
  20. package/locales/ja-JP/plugin.json +8 -0
  21. package/locales/ko-KR/error.json +4 -1
  22. package/locales/ko-KR/plugin.json +8 -0
  23. package/locales/nl-NL/error.json +4 -1
  24. package/locales/nl-NL/plugin.json +8 -0
  25. package/locales/pl-PL/error.json +4 -1
  26. package/locales/pl-PL/plugin.json +8 -0
  27. package/locales/pt-BR/error.json +4 -1
  28. package/locales/pt-BR/plugin.json +8 -0
  29. package/locales/ru-RU/error.json +4 -1
  30. package/locales/ru-RU/plugin.json +8 -0
  31. package/locales/tr-TR/error.json +4 -1
  32. package/locales/tr-TR/plugin.json +8 -0
  33. package/locales/vi-VN/error.json +4 -1
  34. package/locales/vi-VN/plugin.json +8 -0
  35. package/locales/zh-CN/error.json +3 -0
  36. package/locales/zh-CN/plugin.json +8 -0
  37. package/locales/zh-TW/error.json +4 -1
  38. package/locales/zh-TW/plugin.json +8 -0
  39. package/package.json +1 -1
  40. package/src/config/aiModels/openrouter.ts +298 -6
  41. package/src/config/aiModels/qwen.ts +202 -4
  42. package/src/features/PluginDevModal/MCPManifestForm/index.tsx +28 -7
  43. package/src/features/PluginDevModal/PluginPreview/ApiVisualizer.tsx +12 -4
  44. package/src/features/PluginDevModal/PluginPreview/EmptyState.tsx +2 -3
  45. package/src/libs/agent-runtime/qwen/index.ts +7 -3
  46. package/src/libs/mcp/types.ts +1 -1
  47. package/src/locales/default/error.ts +3 -0
  48. package/src/locales/default/plugin.ts +8 -0
  49. package/src/server/routers/desktop/mcp.ts +10 -1
  50. package/src/server/routers/tools/mcp.ts +11 -1
  51. package/src/server/services/mcp/index.ts +35 -12
  52. package/src/services/mcp.ts +14 -3
  53. package/src/types/tool/plugin.ts +12 -2
@@ -1,13 +1,202 @@
1
1
  import { AIChatModelCard } from '@/types/aiModel';
2
2
 
3
- // https://help.aliyun.com/zh/model-studio/developer-reference/use-qwen-by-calling-api#e1fada1a719u7
3
+ // https://help.aliyun.com/zh/model-studio/models?spm=a2c4g.11186623
4
4
 
5
5
  const qwenChatModels: AIChatModelCard[] = [
6
6
  {
7
7
  abilities: {
8
+ functionCall: true,
9
+ reasoning: true,
10
+ },
11
+ contextWindowTokens: 131_072,
12
+ description:
13
+ 'Qwen3是一款能力大幅提升的新一代通义千问大模型,在推理、通用、Agent和多语言等多个核心能力上均达到业界领先水平,并支持思考模式切换。',
14
+ displayName: 'Qwen3 235B A22B',
15
+ enabled: true,
16
+ id: 'qwen3-235b-a22b',
17
+ maxOutput: 8192,
18
+ organization: 'Qwen',
19
+ pricing: { // Thinking mode pricing
20
+ currency: 'CNY',
21
+ input: 40,
22
+ output: 12,
23
+ },
24
+ releasedAt: '2025-04-28',
25
+ settings: {
26
+ extendParams: ['enableReasoning'],
27
+ },
28
+ type: 'chat',
29
+ },
30
+ {
31
+ abilities: {
32
+ functionCall: true,
33
+ reasoning: true,
34
+ },
35
+ contextWindowTokens: 131_072,
36
+ description:
37
+ 'Qwen3是一款能力大幅提升的新一代通义千问大模型,在推理、通用、Agent和多语言等多个核心能力上均达到业界领先水平,并支持思考模式切换。',
38
+ displayName: 'Qwen3 32B',
39
+ enabled: true,
40
+ id: 'qwen3-32b',
41
+ maxOutput: 8192,
42
+ organization: 'Qwen',
43
+ pricing: { // Thinking mode pricing
44
+ currency: 'CNY',
45
+ input: 20,
46
+ output: 8,
47
+ },
48
+ releasedAt: '2025-04-28',
49
+ settings: {
50
+ extendParams: ['enableReasoning'],
51
+ },
52
+ type: 'chat',
53
+ },
54
+ {
55
+ abilities: {
56
+ functionCall: true,
57
+ reasoning: true,
58
+ },
59
+ contextWindowTokens: 131_072,
60
+ description:
61
+ 'Qwen3是一款能力大幅提升的新一代通义千问大模型,在推理、通用、Agent和多语言等多个核心能力上均达到业界领先水平,并支持思考模式切换。',
62
+ displayName: 'Qwen3 30B A3B',
63
+ enabled: true,
64
+ id: 'qwen3-30b-a3b',
65
+ maxOutput: 8192,
66
+ organization: 'Qwen',
67
+ pricing: { // Thinking mode pricing
68
+ currency: 'CNY',
69
+ input: 15,
70
+ output: 6,
71
+ },
72
+ releasedAt: '2025-04-28',
73
+ settings: {
74
+ extendParams: ['enableReasoning'],
75
+ },
76
+ type: 'chat',
77
+ },
78
+ {
79
+ abilities: {
80
+ functionCall: true,
81
+ reasoning: true,
82
+ },
83
+ contextWindowTokens: 131_072,
84
+ description:
85
+ 'Qwen3是一款能力大幅提升的新一代通义千问大模型,在推理、通用、Agent和多语言等多个核心能力上均达到业界领先水平,并支持思考模式切换。',
86
+ displayName: 'Qwen3 14B',
87
+ id: 'qwen3-14b',
88
+ maxOutput: 8192,
89
+ organization: 'Qwen',
90
+ pricing: { // Thinking mode pricing
91
+ currency: 'CNY',
92
+ input: 10,
93
+ output: 4,
94
+ },
95
+ releasedAt: '2025-04-28',
96
+ settings: {
97
+ extendParams: ['enableReasoning'],
98
+ },
99
+ type: 'chat',
100
+ },
101
+ {
102
+ abilities: {
103
+ functionCall: true,
104
+ reasoning: true,
105
+ },
106
+ contextWindowTokens: 131_072,
107
+ description:
108
+ 'Qwen3是一款能力大幅提升的新一代通义千问大模型,在推理、通用、Agent和多语言等多个核心能力上均达到业界领先水平,并支持思考模式切换。',
109
+ displayName: 'Qwen3 8B',
110
+ id: 'qwen3-8b',
111
+ maxOutput: 8192,
112
+ organization: 'Qwen',
113
+ pricing: { // Thinking mode pricing
114
+ currency: 'CNY',
115
+ input: 5,
116
+ output: 2,
117
+ },
118
+ releasedAt: '2025-04-28',
119
+ settings: {
120
+ extendParams: ['enableReasoning'],
121
+ },
122
+ type: 'chat',
123
+ },
124
+ {
125
+ abilities: {
126
+ functionCall: true,
8
127
  reasoning: true,
9
128
  },
10
129
  contextWindowTokens: 131_072,
130
+ description:
131
+ 'Qwen3是一款能力大幅提升的新一代通义千问大模型,在推理、通用、Agent和多语言等多个核心能力上均达到业界领先水平,并支持思考模式切换。',
132
+ displayName: 'Qwen3 4B',
133
+ id: 'qwen3-4b',
134
+ maxOutput: 8192,
135
+ organization: 'Qwen',
136
+ pricing: { // Thinking mode pricing
137
+ currency: 'CNY',
138
+ input: 3,
139
+ output: 1.2,
140
+ },
141
+ releasedAt: '2025-04-28',
142
+ settings: {
143
+ extendParams: ['enableReasoning'],
144
+ },
145
+ type: 'chat',
146
+ },
147
+ {
148
+ abilities: {
149
+ functionCall: true,
150
+ reasoning: true,
151
+ },
152
+ contextWindowTokens: 32_768,
153
+ description:
154
+ 'Qwen3是一款能力大幅提升的新一代通义千问大模型,在推理、通用、Agent和多语言等多个核心能力上均达到业界领先水平,并支持思考模式切换。',
155
+ displayName: 'Qwen3 1.7B',
156
+ id: 'qwen3-1.7b',
157
+ maxOutput: 8192,
158
+ organization: 'Qwen',
159
+ pricing: { // Thinking mode pricing
160
+ currency: 'CNY',
161
+ input: 3,
162
+ output: 1.2,
163
+ },
164
+ releasedAt: '2025-04-28',
165
+ settings: {
166
+ extendParams: ['enableReasoning'],
167
+ },
168
+ type: 'chat',
169
+ },
170
+ {
171
+ abilities: {
172
+ functionCall: true,
173
+ reasoning: true,
174
+ },
175
+ contextWindowTokens: 32_768,
176
+ description:
177
+ 'Qwen3是一款能力大幅提升的新一代通义千问大模型,在推理、通用、Agent和多语言等多个核心能力上均达到业界领先水平,并支持思考模式切换。',
178
+ displayName: 'Qwen3 0.6B',
179
+ id: 'qwen3-0.6b',
180
+ maxOutput: 8192,
181
+ organization: 'Qwen',
182
+ pricing: { // Thinking mode pricing
183
+ currency: 'CNY',
184
+ input: 3,
185
+ output: 1.2,
186
+ },
187
+ releasedAt: '2025-04-28',
188
+ settings: {
189
+ extendParams: ['enableReasoning'],
190
+ },
191
+ type: 'chat',
192
+ },
193
+ {
194
+ abilities: {
195
+ functionCall: true,
196
+ reasoning: true,
197
+ search: true,
198
+ },
199
+ contextWindowTokens: 131_072,
11
200
  description:
12
201
  '基于 Qwen2.5 模型训练的 QwQ 推理模型,通过强化学习大幅度提升了模型推理能力。模型数学代码等核心指标(AIME 24/25、LiveCodeBench)以及部分通用指标(IFEval、LiveBench等)达到DeepSeek-R1 满血版水平。',
13
202
  displayName: 'QwQ Plus',
@@ -29,6 +218,7 @@ const qwenChatModels: AIChatModelCard[] = [
29
218
  {
30
219
  abilities: {
31
220
  functionCall: true,
221
+ reasoning: true,
32
222
  search: true,
33
223
  },
34
224
  contextWindowTokens: 1_000_000,
@@ -41,9 +231,11 @@ const qwenChatModels: AIChatModelCard[] = [
41
231
  pricing: {
42
232
  currency: 'CNY',
43
233
  input: 0.3,
44
- output: 0.6,
234
+ output: 6, // Thinking mode pricing
45
235
  },
236
+ releasedAt: '2025-04-28',
46
237
  settings: {
238
+ extendParams: ['enableReasoning'],
47
239
  searchImpl: 'params',
48
240
  },
49
241
  type: 'chat',
@@ -51,6 +243,7 @@ const qwenChatModels: AIChatModelCard[] = [
51
243
  {
52
244
  abilities: {
53
245
  functionCall: true,
246
+ reasoning: true,
54
247
  search: true,
55
248
  },
56
249
  contextWindowTokens: 131_072,
@@ -63,9 +256,11 @@ const qwenChatModels: AIChatModelCard[] = [
63
256
  pricing: {
64
257
  currency: 'CNY',
65
258
  input: 0.8,
66
- output: 2,
259
+ output: 16, // Thinking mode pricing
67
260
  },
261
+ releasedAt: '2025-04-28',
68
262
  settings: {
263
+ extendParams: ['enableReasoning'],
69
264
  searchImpl: 'params',
70
265
  },
71
266
  type: 'chat',
@@ -101,7 +296,6 @@ const qwenChatModels: AIChatModelCard[] = [
101
296
  description:
102
297
  '通义千问超大规模语言模型,支持长文本上下文,以及基于长文档、多文档等多个场景的对话功能。',
103
298
  displayName: 'Qwen Long',
104
- enabled: true,
105
299
  id: 'qwen-long',
106
300
  maxOutput: 6000,
107
301
  organization: 'Qwen',
@@ -263,6 +457,7 @@ const qwenChatModels: AIChatModelCard[] = [
263
457
  {
264
458
  abilities: {
265
459
  reasoning: true,
460
+ search: true,
266
461
  },
267
462
  contextWindowTokens: 131_072,
268
463
  description:
@@ -277,6 +472,9 @@ const qwenChatModels: AIChatModelCard[] = [
277
472
  output: 6,
278
473
  },
279
474
  releasedAt: '2025-03-06',
475
+ settings: {
476
+ searchImpl: 'params',
477
+ },
280
478
  type: 'chat',
281
479
  },
282
480
  {
@@ -8,7 +8,7 @@ import {
8
8
  } from '@icons-pack/react-simple-icons';
9
9
  import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
10
10
  import { Alert, AutoComplete, FormItem, Input, TextArea } from '@lobehub/ui';
11
- import { Button, Form, FormInstance } from 'antd';
11
+ import { Button, Divider, Form, FormInstance } from 'antd';
12
12
  import { FC, useState } from 'react';
13
13
  import { useTranslation } from 'react-i18next';
14
14
  import { Flexbox } from 'react-layout-kit';
@@ -52,6 +52,7 @@ const STDIO_COMMAND = ['customParams', 'mcp', 'command'];
52
52
  const STDIO_ARGS = ['customParams', 'mcp', 'args'];
53
53
  const STDIO_ENV = ['customParams', 'mcp', 'env'];
54
54
  const MCP_TYPE = ['customParams', 'mcp', 'type'];
55
+ const DESC_TYPE = ['customParams', 'description'];
55
56
 
56
57
  const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
57
58
  const { t } = useTranslation('plugin');
@@ -150,16 +151,24 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
150
151
  const values = form.getFieldsValue();
151
152
  const id = values.identifier;
152
153
  const mcp = values.customParams?.mcp;
154
+ const description = values.customParams?.description;
155
+ const avatar = values.customParams?.avatar;
153
156
 
154
157
  let data: LobeChatPluginManifest;
155
158
 
156
159
  if (mcp.type === 'http') {
157
160
  if (!mcp.url) throw new Error(t('dev.mcp.url.required'));
158
- data = await mcpService.getStreamableMcpServerManifest(id, mcp.url);
161
+ data = await mcpService.getStreamableMcpServerManifest(id, mcp.url, {
162
+ avatar,
163
+ description,
164
+ });
159
165
  } else if (mcp.type === 'stdio') {
160
166
  if (!mcp.command) throw new Error(t('dev.mcp.command.required'));
161
167
  if (!mcp.args) throw new Error(t('dev.mcp.args.required'));
162
- data = await mcpService.getStdioMcpServerManifest(id, mcp.command, mcp.args);
168
+ data = await mcpService.getStdioMcpServerManifest(id, mcp.command, mcp.args, {
169
+ avatar,
170
+ description,
171
+ });
163
172
  } else {
164
173
  throw new Error('Invalid MCP type'); // Internal error
165
174
  }
@@ -253,7 +262,6 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
253
262
  >
254
263
  <MCPTypeSelect />
255
264
  </Form.Item>
256
-
257
265
  <FormItem
258
266
  desc={t('dev.mcp.identifier.desc')}
259
267
  label={t('dev.mcp.identifier.label')}
@@ -281,7 +289,6 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
281
289
  >
282
290
  <Input placeholder={t('dev.mcp.identifier.placeholder')} />
283
291
  </FormItem>
284
-
285
292
  {mcpType === 'http' && (
286
293
  <FormItem
287
294
  desc={t('dev.mcp.url.desc')}
@@ -296,7 +303,6 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
296
303
  <Input placeholder="https://mcp.higress.ai/mcp-github/xxxxx" />
297
304
  </FormItem>
298
305
  )}
299
-
300
306
  {mcpType === 'stdio' && (
301
307
  <>
302
308
  <FormItem
@@ -349,7 +355,6 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
349
355
  </Button>
350
356
  </Flexbox>
351
357
  </FormItem>
352
-
353
358
  {connectionError && (
354
359
  <Alert
355
360
  closable
@@ -361,6 +366,22 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
361
366
  />
362
367
  )}
363
368
  <FormItem name={'manifest'} noStyle />
369
+ <Divider />
370
+ <FormItem
371
+ desc={t('dev.mcp.desc.desc')}
372
+ label={t('dev.mcp.desc.label')}
373
+ name={DESC_TYPE}
374
+ tag={'description'}
375
+ >
376
+ <Input placeholder={t('dev.mcp.desc.placeholder')} />
377
+ </FormItem>
378
+ <FormItem
379
+ label={t('dev.mcp.avatar.label')}
380
+ name={['customParams', 'avatar']}
381
+ tag={'avatar'}
382
+ >
383
+ <Input placeholder={'https://plugin-avatar.com'} />
384
+ </FormItem>
364
385
  </Flexbox>
365
386
  </Form>
366
387
  </>
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Block, Icon, Tag } from '@lobehub/ui';
4
- import { Input, Space, Typography } from 'antd';
4
+ import { Input, Space } from 'antd';
5
5
  import { createStyles } from 'antd-style';
6
6
  import { ChevronDown, ChevronRight } from 'lucide-react';
7
7
  import { memo, useState } from 'react';
@@ -9,13 +9,21 @@ import { useTranslation } from 'react-i18next';
9
9
  import { Flexbox } from 'react-layout-kit';
10
10
 
11
11
  const useStyles = createStyles(({ css, token }) => ({
12
+ apiDesc: css`
13
+ overflow: hidden;
14
+ display: -webkit-box;
15
+ -webkit-box-orient: vertical;
16
+ -webkit-line-clamp: 2;
17
+
18
+ font-size: 12px;
19
+ color: ${token.colorTextTertiary};
20
+ `,
12
21
  apiHeader: css`
13
22
  cursor: pointer;
14
23
  display: flex;
15
24
  align-items: center;
16
25
  justify-content: space-between;
17
26
  `,
18
-
19
27
  apiTitle: css`
20
28
  font-family: ${token.fontFamilyCode};
21
29
  `,
@@ -99,9 +107,9 @@ const ApiItem = memo<ApiItemProps>(({ api }) => {
99
107
  return (
100
108
  <Block gap={8} padding={16}>
101
109
  <div className={styles.apiHeader} onClick={() => setExpanded(!expanded)}>
102
- <Flexbox gap={4}>
110
+ <Flexbox gap={8}>
103
111
  <div className={styles.apiTitle}>{api.name}</div>
104
- <Typography.Text type="secondary">{api.description}</Typography.Text>
112
+ <div className={styles.apiDesc}>{api.description}</div>
105
113
  </Flexbox>
106
114
 
107
115
  <Icon icon={expanded ? ChevronDown : ChevronRight} />
@@ -11,6 +11,7 @@ const useStyles = createStyles(({ token, css }) => ({
11
11
  container: css`
12
12
  display: flex;
13
13
  flex-direction: column;
14
+ gap: 12px;
14
15
  align-items: center;
15
16
  justify-content: center;
16
17
 
@@ -19,7 +20,6 @@ const useStyles = createStyles(({ token, css }) => ({
19
20
  padding: ${token.paddingLG}px;
20
21
  `,
21
22
  description: css`
22
- max-width: 320px;
23
23
  color: ${token.colorTextSecondary};
24
24
  text-align: center;
25
25
  `,
@@ -30,7 +30,6 @@ const useStyles = createStyles(({ token, css }) => ({
30
30
 
31
31
  width: 64px;
32
32
  height: 64px;
33
- margin-block-end: ${token.marginMD}px;
34
33
  border-radius: 50%;
35
34
 
36
35
  background-color: ${token.colorPrimaryBg};
@@ -68,7 +67,7 @@ export default function PluginEmptyState() {
68
67
  {t('dev.preview.empty.title')}
69
68
  </Title>
70
69
  <Paragraph className={styles.description}>{t('dev.preview.empty.desc')}</Paragraph>
71
- <Space align="center" direction="vertical" style={{ marginTop: 24 }}>
70
+ <Space align="center" direction="vertical">
72
71
  <div className={styles.line} style={{ width: 128 }} />
73
72
  <div className={styles.line} style={{ width: 96 }} />
74
73
  <div className={styles.line} style={{ width: 48 }} />
@@ -24,10 +24,14 @@ export const LobeQwenAI = LobeOpenAICompatibleFactory({
24
24
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
25
25
  chatCompletion: {
26
26
  handlePayload: (payload) => {
27
- const { model, presence_penalty, temperature, top_p, enabledSearch, ...rest } = payload;
27
+ const { model, presence_penalty, temperature, thinking, top_p, enabledSearch, ...rest } = payload;
28
28
 
29
29
  return {
30
30
  ...rest,
31
+ ...( ['qwen3','qwen-turbo','qwen-plus']
32
+ .some(keyword => model.toLowerCase().includes(keyword))
33
+ ? { enable_thinking: thinking !== undefined ? thinking.type === 'enabled' : false }
34
+ : {}),
31
35
  frequency_penalty: undefined,
32
36
  model,
33
37
  presence_penalty: QwenLegacyModels.has(model)
@@ -70,11 +74,11 @@ export const LobeQwenAI = LobeOpenAICompatibleFactory({
70
74
  models: async ({ client }) => {
71
75
  const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
72
76
 
73
- const functionCallKeywords = ['qwen-max', 'qwen-plus', 'qwen-turbo', 'qwen2.5'];
77
+ const functionCallKeywords = ['qwen-max', 'qwen-plus', 'qwen-turbo', 'qwen-long', 'qwen1.5', 'qwen2', 'qwen2.5', 'qwen3'];
74
78
 
75
79
  const visionKeywords = ['qvq', 'vl'];
76
80
 
77
- const reasoningKeywords = ['qvq', 'qwq', 'deepseek-r1'];
81
+ const reasoningKeywords = ['qvq', 'qwq', 'deepseek-r1', 'qwen3'];
78
82
 
79
83
  const modelsPage = (await client.models.list()) as any;
80
84
  const modelList: QwenModelCard[] = modelsPage.data;
@@ -17,7 +17,7 @@ interface HttpMCPClientParams {
17
17
  url: string;
18
18
  }
19
19
 
20
- interface StdioMCPParams {
20
+ export interface StdioMCPParams {
21
21
  args: string[];
22
22
  command: string;
23
23
  env?: Record<string, string>;
@@ -66,6 +66,7 @@ export default {
66
66
  429: '很抱歉,您的请求太多,服务器有点累了,请稍后再试',
67
67
  431: '很抱歉,您的请求头字段太大,服务器无法处理',
68
68
  451: '很抱歉,由于法律原因,服务器拒绝提供此资源',
69
+ 499: '很抱歉,您的请求在服务器处理中被意外中断,可能是因为您主动取消了操作或网络连接不稳定。请检查网络状况后重试。',
69
70
  500: '很抱歉,服务器似乎遇到了一些困难,暂时无法完成您的请求,请稍后再试',
70
71
  501: '很抱歉,服务器还不知道如何处理这个请求,请确认您的操作是否正确',
71
72
  502: '很抱歉,服务器似乎迷失了方向,暂时无法提供服务,请稍后再试',
@@ -76,6 +77,8 @@ export default {
76
77
  507: '很抱歉,服务器存储空间不足,无法处理您的请求,请稍后再试',
77
78
  509: '很抱歉,服务器的带宽已用尽,请稍后再试',
78
79
  510: '很抱歉,服务器不支持请求的扩展功能,请联系管理员',
80
+ 520: '很抱歉,服务器遇到了一个意外的问题,导致无法完成您的请求。请稍后再试,我们正努力解决这个问题。',
81
+ 522: '很抱歉,服务器连接超时,未能及时响应您的请求。可能是网络不稳定或服务器暂时无法访问。请稍后再试,我们正在努力恢复服务。',
79
82
  524: '很抱歉,服务器在等回复时超时了,可能是因为响应太慢,请稍后再试',
80
83
 
81
84
  /* eslint-disable sort-keys-fix/sort-keys-fix */
@@ -54,12 +54,20 @@ export default {
54
54
  placeholder: '例如:mcp-hello-world',
55
55
  required: '请输入启动参数',
56
56
  },
57
+ avatar: {
58
+ label: '插件图标',
59
+ },
57
60
  command: {
58
61
  desc: '用于启动 MCP STDIO Server 的可执行文件或脚本',
59
62
  label: '命令',
60
63
  placeholder: '例如:npx / uv / docker 等',
61
64
  required: '请输入启动命令',
62
65
  },
66
+ desc: {
67
+ desc: '添加插件的描述说明',
68
+ label: '插件描述',
69
+ placeholder: '补充该插件的使用说明和场景等信息',
70
+ },
63
71
  endpoint: {
64
72
  desc: '输入你的 MCP Streamable HTTP Server 的地址',
65
73
  label: 'MCP Endpoint URL',
@@ -8,6 +8,12 @@ import { mcpService } from '@/server/services/mcp';
8
8
  const stdioParamsSchema = z.object({
9
9
  args: z.array(z.string()).optional().default([]),
10
10
  command: z.string().min(1),
11
+ metadata: z
12
+ .object({
13
+ avatar: z.string().optional(),
14
+ description: z.string().optional(),
15
+ })
16
+ .optional(),
11
17
  name: z.string().min(1),
12
18
  type: z.literal('stdio').default('stdio'),
13
19
  });
@@ -16,7 +22,10 @@ const mcpProcedure = isServerMode ? authedProcedure : passwordProcedure;
16
22
 
17
23
  export const mcpRouter = router({
18
24
  getStdioMcpServerManifest: mcpProcedure.input(stdioParamsSchema).query(async ({ input }) => {
19
- return await mcpService.getStdioMcpServerManifest(input.name, input.command, input.args);
25
+ return await mcpService.getStdioMcpServerManifest(
26
+ { args: input.args, command: input.command, name: input.name },
27
+ input.metadata,
28
+ );
20
29
  }),
21
30
 
22
31
  /* eslint-disable sort-keys-fix/sort-keys-fix */
@@ -39,11 +39,21 @@ export const mcpRouter = router({
39
39
  .input(
40
40
  z.object({
41
41
  identifier: z.string(),
42
+ metadata: z
43
+ .object({
44
+ avatar: z.string().optional(),
45
+ description: z.string().optional(),
46
+ })
47
+ .optional(),
42
48
  url: z.string().url(),
43
49
  }),
44
50
  )
45
51
  .query(async ({ input }) => {
46
- return await mcpService.getStreamableMcpServerManifest(input.identifier, input.url);
52
+ return await mcpService.getStreamableMcpServerManifest(
53
+ input.identifier,
54
+ input.url,
55
+ input.metadata,
56
+ );
47
57
  }),
48
58
  /* eslint-disable sort-keys-fix/sort-keys-fix */
49
59
  // --- MCP Interaction ---
@@ -3,7 +3,8 @@ import { McpError } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { TRPCError } from '@trpc/server';
4
4
  import debug from 'debug';
5
5
 
6
- import { MCPClient, MCPClientParams } from '@/libs/mcp';
6
+ import { MCPClient, MCPClientParams, StdioMCPParams } from '@/libs/mcp';
7
+ import { CustomPluginMetadata } from '@/types/tool/plugin';
7
8
  import { safeParseJSON } from '@/utils/safeParseJSON';
8
9
 
9
10
  const log = debug('lobe-mcp:service');
@@ -58,9 +59,20 @@ class MCPService {
58
59
  const result = await client.callTool(toolName, args); // Pass args directly
59
60
  log(`Tool "${toolName}" called successfully for params: %O, result: %O`, params, result);
60
61
  const { content, isError } = result;
61
- if (!isError) return content;
62
62
 
63
- return result;
63
+ if (isError) return result;
64
+
65
+ const data = content as { text: string; type: 'text' }[];
66
+
67
+ const text = data?.[0]?.text;
68
+
69
+ if (!text) return data;
70
+
71
+ // try to get json object, which will be stringify in the client
72
+ const json = safeParseJSON(text);
73
+ if (json) return json;
74
+
75
+ return text;
64
76
  } catch (error) {
65
77
  if (error instanceof McpError) {
66
78
  const mcpError = error as McpError;
@@ -143,6 +155,7 @@ class MCPService {
143
155
  async getStreamableMcpServerManifest(
144
156
  identifier: string,
145
157
  url: string,
158
+ metadata?: CustomPluginMetadata,
146
159
  ): Promise<LobeChatPluginManifest> {
147
160
  const tools = await this.listTools({ name: identifier, type: 'http', url }); // Get client using params
148
161
 
@@ -150,27 +163,37 @@ class MCPService {
150
163
  api: tools,
151
164
  identifier,
152
165
  meta: {
153
- avatar: 'MCP_AVATAR',
154
- description: `${identifier} MCP server has ${tools.length} tools, like "${tools[0]?.name}"`,
166
+ avatar: metadata?.avatar || 'MCP_AVATAR',
167
+ description:
168
+ metadata?.description ||
169
+ `${identifier} MCP server has ${tools.length} tools, like "${tools[0]?.name}"`,
155
170
  title: identifier,
156
171
  },
157
172
  // TODO: temporary
158
173
  type: 'mcp' as any,
159
174
  };
160
175
  }
176
+
161
177
  async getStdioMcpServerManifest(
162
- identifier: string,
163
- command: string,
164
- args: string[],
178
+ params: Omit<StdioMCPParams, 'type'>,
179
+ metadata?: CustomPluginMetadata,
165
180
  ): Promise<LobeChatPluginManifest> {
166
- const tools = await this.listTools({ args, command, name: identifier, type: 'stdio' }); // Get client using params
167
-
181
+ const tools = await this.listTools({
182
+ args: params.args,
183
+ command: params.command,
184
+ name: params.name,
185
+ type: 'stdio',
186
+ });
187
+
188
+ const identifier = params.name;
168
189
  return {
169
190
  api: tools,
170
191
  identifier,
171
192
  meta: {
172
- avatar: 'MCP_AVATAR',
173
- description: `${identifier} MCP server has ${tools.length} tools, like "${tools[0]?.name}"`,
193
+ avatar: metadata?.avatar || 'MCP_AVATAR',
194
+ description:
195
+ metadata?.description ||
196
+ `${identifier} MCP server has ${tools.length} tools, like "${tools[0]?.name}"`,
174
197
  title: identifier,
175
198
  },
176
199
  // TODO: temporary