@lobehub/lobehub 2.0.0-next.27 → 2.0.0-next.29

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 (34) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/package.json +1 -1
  4. package/packages/database/package.json +1 -1
  5. package/packages/database/src/models/message.ts +29 -2
  6. package/packages/types/src/discover/mcp.ts +6 -0
  7. package/packages/types/src/message/ui/params.ts +0 -49
  8. package/packages/types/src/plugins/mcp.ts +4 -1
  9. package/renovate.json +4 -30
  10. package/src/features/MCP/utils.test.ts +91 -0
  11. package/src/features/MCP/utils.ts +20 -2
  12. package/src/features/PluginStore/Content.tsx +2 -3
  13. package/src/features/PluginStore/McpList/index.tsx +6 -2
  14. package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +35 -35
  15. package/src/server/routers/lambda/market/index.ts +4 -2
  16. package/src/server/routers/lambda/message.ts +104 -16
  17. package/src/services/mcp.ts +40 -6
  18. package/src/services/message/index.ts +63 -35
  19. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +17 -10
  20. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +4 -4
  21. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +2 -2
  22. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +3 -2
  23. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +2 -2
  24. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +7 -2
  25. package/src/store/chat/slices/builtinTool/actions/search.ts +3 -3
  26. package/src/store/chat/slices/message/action.test.ts +152 -15
  27. package/src/store/chat/slices/message/action.ts +70 -82
  28. package/src/store/chat/slices/plugin/action.test.ts +84 -25
  29. package/src/store/chat/slices/plugin/action.ts +52 -24
  30. package/src/store/chat/slices/thread/action.test.ts +13 -4
  31. package/src/store/chat/slices/thread/action.ts +3 -1
  32. package/src/store/tool/slices/mcpStore/action.test.ts +95 -3
  33. package/src/store/tool/slices/mcpStore/action.ts +177 -53
  34. package/src/store/tool/slices/oldStore/initialState.ts +1 -2
@@ -764,7 +764,7 @@ describe('thread action', () => {
764
764
 
765
765
  const createMessageSpy = vi
766
766
  .spyOn(result.current, 'internal_createMessage')
767
- .mockResolvedValue('new-msg-id');
767
+ .mockResolvedValue({ id: 'new-msg-id', messages: [] });
768
768
  const coreProcessSpy = vi
769
769
  .spyOn(result.current, 'internal_coreProcessMessage')
770
770
  .mockResolvedValue();
@@ -803,7 +803,10 @@ describe('thread action', () => {
803
803
  });
804
804
  });
805
805
 
806
- vi.spyOn(result.current, 'internal_createMessage').mockResolvedValue('new-msg-id');
806
+ vi.spyOn(result.current, 'internal_createMessage').mockResolvedValue({
807
+ id: 'new-msg-id',
808
+ messages: [],
809
+ });
807
810
  vi.spyOn(result.current, 'internal_coreProcessMessage').mockResolvedValue();
808
811
  vi.spyOn(result.current, 'internal_createTmpMessage').mockReturnValue('temp-msg-id');
809
812
  vi.spyOn(result.current, 'internal_toggleMessageLoading');
@@ -833,7 +836,10 @@ describe('thread action', () => {
833
836
  });
834
837
  });
835
838
 
836
- vi.spyOn(result.current, 'internal_createMessage').mockResolvedValue('new-msg-id');
839
+ vi.spyOn(result.current, 'internal_createMessage').mockResolvedValue({
840
+ id: 'new-msg-id',
841
+ messages: [],
842
+ });
837
843
  vi.spyOn(result.current, 'internal_coreProcessMessage').mockResolvedValue();
838
844
  vi.spyOn(result.current, 'internal_createTmpMessage').mockReturnValue('temp-msg-id');
839
845
  vi.spyOn(result.current, 'internal_toggleMessageLoading');
@@ -855,7 +861,10 @@ describe('thread action', () => {
855
861
  });
856
862
 
857
863
  vi.spyOn(result.current, 'internal_shouldUseRAG').mockReturnValue(true);
858
- vi.spyOn(result.current, 'internal_createMessage').mockResolvedValue('new-msg-id');
864
+ vi.spyOn(result.current, 'internal_createMessage').mockResolvedValue({
865
+ id: 'new-msg-id',
866
+ messages: [],
867
+ });
859
868
  vi.spyOn(result.current, 'internal_createTmpMessage').mockReturnValue('temp-msg-id');
860
869
  vi.spyOn(result.current, 'internal_toggleMessageLoading');
861
870
 
@@ -158,7 +158,9 @@ export const chatThreadMessage: StateCreator<
158
158
  tempMessageId = get().internal_createTmpMessage(newMessage);
159
159
  get().internal_toggleMessageLoading(true, tempMessageId);
160
160
 
161
- parentMessageId = await get().internal_createMessage(newMessage, { tempMessageId });
161
+ const result = await get().internal_createMessage(newMessage, { tempMessageId });
162
+ if (!result) return;
163
+ parentMessageId = result.id;
162
164
  }
163
165
 
164
166
  get().internal_toggleMessageLoading(false, tempMessageId);
@@ -20,6 +20,44 @@ vi.mock('@/utils/sleep', () => ({
20
20
  sleep: vi.fn().mockResolvedValue(undefined),
21
21
  }));
22
22
 
23
+ const ORIGINAL_DESKTOP_ENV = process.env.NEXT_PUBLIC_IS_DESKTOP_APP;
24
+
25
+ const bootstrapToolStoreWithDesktop = async (isDesktopEnv: boolean) => {
26
+ vi.resetModules();
27
+ vi.mock('zustand/traditional');
28
+ process.env.NEXT_PUBLIC_IS_DESKTOP_APP = isDesktopEnv ? '1' : '0';
29
+
30
+ vi.doMock('@lobechat/const', async () => {
31
+ const actual = await vi.importActual<typeof import('@lobechat/const')>('@lobechat/const');
32
+ return {
33
+ ...actual,
34
+ isDesktop: isDesktopEnv,
35
+ };
36
+ });
37
+
38
+ const storeModule = await import('@/store/tool');
39
+ const discoverModule = await import('@/services/discover');
40
+ const helpersModule = await import('@/store/global/helpers');
41
+
42
+ const cleanup = () => {
43
+ vi.resetModules();
44
+ vi.doUnmock('@lobechat/const');
45
+ vi.mock('zustand/traditional');
46
+ if (ORIGINAL_DESKTOP_ENV === undefined) {
47
+ delete process.env.NEXT_PUBLIC_IS_DESKTOP_APP;
48
+ } else {
49
+ process.env.NEXT_PUBLIC_IS_DESKTOP_APP = ORIGINAL_DESKTOP_ENV;
50
+ }
51
+ };
52
+
53
+ return {
54
+ useToolStore: storeModule.useToolStore,
55
+ discoverService: discoverModule.discoverService,
56
+ globalHelpers: helpersModule.globalHelpers,
57
+ cleanup,
58
+ };
59
+ };
60
+
23
61
  beforeEach(() => {
24
62
  vi.clearAllMocks();
25
63
 
@@ -48,6 +86,14 @@ afterEach(() => {
48
86
  vi.restoreAllMocks();
49
87
  });
50
88
 
89
+ afterAll(() => {
90
+ if (ORIGINAL_DESKTOP_ENV === undefined) {
91
+ delete process.env.NEXT_PUBLIC_IS_DESKTOP_APP;
92
+ } else {
93
+ process.env.NEXT_PUBLIC_IS_DESKTOP_APP = ORIGINAL_DESKTOP_ENV;
94
+ }
95
+ });
96
+
51
97
  describe('mcpStore actions', () => {
52
98
  describe('updateMCPInstallProgress', () => {
53
99
  it('should update install progress for an identifier', () => {
@@ -487,7 +533,9 @@ describe('mcpStore actions', () => {
487
533
  expect(result.current.data).toEqual(mockData);
488
534
  });
489
535
 
490
- expect(discoverService.getMCPPluginList).toHaveBeenCalledWith({ page: 1, pageSize: 20 });
536
+ expect(discoverService.getMCPPluginList).toHaveBeenCalledWith(
537
+ expect.objectContaining({ page: 1, pageSize: 20, connectionType: 'http' }),
538
+ );
491
539
 
492
540
  const state = useToolStore.getState();
493
541
  expect(state.mcpPluginItems).toEqual(mockData.items);
@@ -542,7 +590,9 @@ describe('mcpStore actions', () => {
542
590
  renderHook(() => useToolStore.getState().useFetchMCPPluginList(params));
543
591
 
544
592
  await waitFor(() => {
545
- expect(discoverService.getMCPPluginList).toHaveBeenCalledWith(params);
593
+ expect(discoverService.getMCPPluginList).toHaveBeenCalledWith(
594
+ expect.objectContaining({ ...params, connectionType: 'http' }),
595
+ );
546
596
  });
547
597
  });
548
598
 
@@ -561,9 +611,51 @@ describe('mcpStore actions', () => {
561
611
  renderHook(() => useToolStore.getState().useFetchMCPPluginList(params));
562
612
 
563
613
  await waitFor(() => {
564
- expect(discoverService.getMCPPluginList).toHaveBeenCalledWith(params);
614
+ expect(discoverService.getMCPPluginList).toHaveBeenCalledWith(
615
+ expect.objectContaining({ ...params, connectionType: 'http' }),
616
+ );
565
617
  });
566
618
  });
619
+
620
+ it('should not append connectionType in desktop environment', async () => {
621
+ const {
622
+ useToolStore: desktopStore,
623
+ discoverService: desktopDiscoverService,
624
+ globalHelpers: desktopGlobalHelpers,
625
+ cleanup,
626
+ } = await bootstrapToolStoreWithDesktop(true);
627
+
628
+ const mockData = {
629
+ items: [{ identifier: 'desktop-plugin', name: 'Desktop Plugin' }] as PluginItem[],
630
+ categories: [],
631
+ totalCount: 1,
632
+ totalPages: 1,
633
+ currentPage: 1,
634
+ pageSize: 20,
635
+ };
636
+
637
+ try {
638
+ vi.spyOn(desktopGlobalHelpers, 'getCurrentLanguage').mockReturnValue('en-US');
639
+ const fetchSpy = vi
640
+ .spyOn(desktopDiscoverService, 'getMCPPluginList')
641
+ .mockResolvedValue(mockData);
642
+
643
+ const { result } = renderHook(() =>
644
+ desktopStore.getState().useFetchMCPPluginList({ page: 1, pageSize: 20 }),
645
+ );
646
+
647
+ await waitFor(() => {
648
+ expect(result.current.data).toEqual(mockData);
649
+ });
650
+
651
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
652
+ const [firstCallArgs] = fetchSpy.mock.calls[0];
653
+ expect(firstCallArgs).toMatchObject({ page: 1, pageSize: 20 });
654
+ expect(firstCallArgs.connectionType).toBeUndefined();
655
+ } finally {
656
+ cleanup();
657
+ }
658
+ });
567
659
  });
568
660
 
569
661
  describe('installMCPPlugin', () => {
@@ -1,19 +1,21 @@
1
1
  import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
2
  import { PluginItem, PluginListResponse } from '@lobehub/market-sdk';
3
3
  import { TRPCClientError } from '@trpc/client';
4
+ import debug from 'debug';
4
5
  import { produce } from 'immer';
5
6
  import { uniqBy } from 'lodash-es';
6
7
  import { gt, valid } from 'semver';
7
8
  import useSWR, { SWRResponse } from 'swr';
8
9
  import { StateCreator } from 'zustand/vanilla';
9
10
 
10
- import { CURRENT_VERSION } from '@/const/version';
11
+ import { CURRENT_VERSION, isDesktop } from '@lobechat/const';
11
12
  import { MCPErrorData } from '@/libs/mcp/types';
12
13
  import { discoverService } from '@/services/discover';
13
14
  import { mcpService } from '@/services/mcp';
14
15
  import { pluginService } from '@/services/plugin';
15
16
  import { globalHelpers } from '@/store/global/helpers';
16
17
  import { mcpStoreSelectors } from '@/store/tool/selectors';
18
+ import { McpConnectionType } from '@/types/discover';
17
19
  import {
18
20
  CheckMcpInstallResult,
19
21
  MCPErrorInfo,
@@ -28,8 +30,41 @@ import { setNamespace } from '@/utils/storeDebug';
28
30
  import { ToolStore } from '../../store';
29
31
  import { MCPStoreState } from './initialState';
30
32
 
33
+ const log = debug('lobe-mcp:store:action');
34
+
31
35
  const n = setNamespace('mcpStore');
32
36
 
37
+ const doesConfigSchemaRequireInput = (configSchema?: any) => {
38
+ if (!configSchema) return false;
39
+
40
+ const hasRequiredArray =
41
+ Array.isArray(configSchema.required) && configSchema.required.some(Boolean);
42
+
43
+ const hasRequiredProperty =
44
+ !!configSchema.properties &&
45
+ Object.values(configSchema.properties).some(
46
+ (property: any) => property && property.required === true,
47
+ );
48
+
49
+ return hasRequiredArray || hasRequiredProperty;
50
+ };
51
+
52
+ const toNonEmptyStringRecord = (input?: Record<string, any>) => {
53
+ if (!input) return undefined;
54
+
55
+ const entries = Object.entries(input).filter(
56
+ ([, value]) => value !== undefined && value !== null,
57
+ );
58
+
59
+ if (entries.length === 0) return undefined;
60
+
61
+ return entries.reduce<Record<string, string>>((acc, [key, value]) => {
62
+ acc[key] = typeof value === 'string' ? value : String(value);
63
+
64
+ return acc;
65
+ }, {});
66
+ };
67
+
33
68
  // 测试连接结果类型
34
69
  export interface TestMcpConnectionResult {
35
70
  error?: string;
@@ -101,6 +136,7 @@ export const createMCPPluginStoreSlice: StateCreator<
101
136
 
102
137
  installMCPPlugin: async (identifier, options = {}) => {
103
138
  const { resume = false, config, skipDepsCheck } = options;
139
+ const normalizedConfig = toNonEmptyStringRecord(config);
104
140
  let plugin = mcpStoreSelectors.getPluginById(identifier)(get());
105
141
 
106
142
  if (!plugin || !plugin.manifestUrl) {
@@ -149,12 +185,8 @@ export const createMCPPluginStoreSlice: StateCreator<
149
185
  }
150
186
 
151
187
  data = configInfo.manifest;
152
- connection = {
153
- ...configInfo.connection,
154
- config, // 合并用户提供的配置
155
- };
188
+ connection = configInfo.connection ? { ...configInfo.connection } : undefined;
156
189
  result = configInfo.checkResult;
157
- connection = configInfo.connection;
158
190
  } else {
159
191
  // 正常模式:从头开始安装
160
192
 
@@ -175,59 +207,137 @@ export const createMCPPluginStoreSlice: StateCreator<
175
207
  install: true,
176
208
  });
177
209
 
178
- // 步骤 2: 检查安装环境
179
- updateMCPInstallProgress(identifier, {
180
- progress: 30,
181
- step: MCPInstallStep.CHECKING_INSTALLATION,
210
+ const deploymentOptions: any[] = Array.isArray(data.deploymentOptions)
211
+ ? data.deploymentOptions
212
+ : [];
213
+
214
+ const httpOption = deploymentOptions.find(
215
+ (option) => option?.connection?.url && option?.connection?.type === 'http',
216
+ ) ||
217
+ deploymentOptions.find(
218
+ (option) => option?.connection?.url && !option?.connection?.type,
219
+ );
220
+
221
+ const hasNonHttpDeployment = deploymentOptions.some((option) => {
222
+ const type = option?.connection?.type;
223
+ if (!type && option?.connection?.url) return false;
224
+
225
+ return type && type !== 'http';
182
226
  });
183
227
 
184
- // 检查是否已被取消
185
- if (abortController.signal.aborted) {
186
- return;
187
- }
228
+ const shouldUseHttpDeployment = !!httpOption && (!hasNonHttpDeployment || !isDesktop);
188
229
 
189
- result = await mcpService.checkInstallation(data, abortController.signal);
230
+ if (shouldUseHttpDeployment && httpOption) {
231
+ // ✅ HTTP 类型:跳过系统依赖检查,直接使用 URL
232
+ log('HTTP MCP detected, skipping system dependency check');
190
233
 
191
- if (!result.success) {
192
- updateMCPInstallProgress(identifier, undefined);
193
- return;
194
- }
234
+ connection = {
235
+ auth: httpOption.connection?.auth || { type: 'none' },
236
+ headers: httpOption.connection?.headers,
237
+ type: 'http',
238
+ url: httpOption.connection?.url,
239
+ };
240
+
241
+ log('Using HTTP connection: %O', { type: connection.type, url: connection.url });
195
242
 
196
- // 步骤 3: 检查系统依赖是否满足
197
- if (!skipDepsCheck && !result.allDependenciesMet) {
198
- // 依赖不满足,暂停安装流程并显示依赖安装引导
243
+ const configSchema = httpOption.connection?.configSchema;
244
+ const needsConfig = doesConfigSchemaRequireInput(configSchema);
245
+
246
+ if (needsConfig && !normalizedConfig) {
247
+ updateMCPInstallProgress(identifier, {
248
+ configSchema,
249
+ connection,
250
+ manifest: data,
251
+ needsConfig: true,
252
+ progress: 50,
253
+ step: MCPInstallStep.CONFIGURATION_REQUIRED,
254
+ });
255
+
256
+ updateInstallLoadingState(identifier, undefined);
257
+ return false;
258
+ }
259
+ } else {
260
+ // ❌ stdio 类型:需要完整的系统依赖检查流程
261
+
262
+ // 步骤 2: 检查安装环境
199
263
  updateMCPInstallProgress(identifier, {
200
- connection: result.connection,
201
- manifest: data,
202
- progress: 40,
203
- step: MCPInstallStep.DEPENDENCIES_REQUIRED,
204
- systemDependencies: result.systemDependencies,
264
+ progress: 30,
265
+ step: MCPInstallStep.CHECKING_INSTALLATION,
205
266
  });
206
267
 
207
- // 暂停安装流程,等待用户安装依赖
208
- updateInstallLoadingState(identifier, undefined);
209
- return false; // 返回 false 表示需要安装依赖
268
+ // 检查是否已被取消
269
+ if (abortController.signal.aborted) {
270
+ return;
271
+ }
272
+
273
+ result = await mcpService.checkInstallation(data, abortController.signal);
274
+
275
+ if (!result.success) {
276
+ updateMCPInstallProgress(identifier, undefined);
277
+ return;
278
+ }
279
+
280
+ // 步骤 3: 检查系统依赖是否满足
281
+ if (!skipDepsCheck && !result.allDependenciesMet) {
282
+ // 依赖不满足,暂停安装流程并显示依赖安装引导
283
+ updateMCPInstallProgress(identifier, {
284
+ connection: result.connection,
285
+ manifest: data,
286
+ progress: 40,
287
+ step: MCPInstallStep.DEPENDENCIES_REQUIRED,
288
+ systemDependencies: result.systemDependencies,
289
+ });
290
+
291
+ // 暂停安装流程,等待用户安装依赖
292
+ updateInstallLoadingState(identifier, undefined);
293
+ return false; // 返回 false 表示需要安装依赖
294
+ }
295
+
296
+ // 步骤 4: 检查是否需要配置
297
+ if (result.needsConfig) {
298
+ // 需要配置,暂停安装流程
299
+ updateMCPInstallProgress(identifier, {
300
+ checkResult: result,
301
+ configSchema: result.configSchema,
302
+ connection: result.connection,
303
+ manifest: data,
304
+ needsConfig: true,
305
+ progress: 50,
306
+ step: MCPInstallStep.CONFIGURATION_REQUIRED,
307
+ });
308
+
309
+ // 暂停安装流程,等待用户配置
310
+ updateInstallLoadingState(identifier, undefined);
311
+ return false; // 返回 false 表示需要配置
312
+ }
313
+
314
+ connection = result.connection;
210
315
  }
316
+ }
211
317
 
212
- // 步骤 4: 检查是否需要配置
213
- if (result.needsConfig) {
214
- // 需要配置,暂停安装流程
215
- updateMCPInstallProgress(identifier, {
216
- checkResult: result,
217
- configSchema: result.configSchema,
218
- connection: result.connection,
219
- manifest: data,
220
- needsConfig: true,
221
- progress: 50,
222
- step: MCPInstallStep.CONFIGURATION_REQUIRED,
223
- });
318
+ let mergedHttpHeaders: Record<string, string> | undefined;
319
+ let mergedStdioEnv: Record<string, string> | undefined;
320
+
321
+ if (connection?.type === 'http') {
322
+ const baseHeaders = toNonEmptyStringRecord(connection.headers);
224
323
 
225
- // 暂停安装流程,等待用户配置
226
- updateInstallLoadingState(identifier, undefined);
227
- return false; // 返回 false 表示需要配置
324
+ if (baseHeaders || normalizedConfig) {
325
+ mergedHttpHeaders = {
326
+ ...baseHeaders,
327
+ ...normalizedConfig,
328
+ };
228
329
  }
330
+ }
331
+
332
+ if (connection?.type === 'stdio') {
333
+ const baseEnv = toNonEmptyStringRecord(connection.env);
229
334
 
230
- connection = result.connection;
335
+ if (baseEnv || normalizedConfig) {
336
+ mergedStdioEnv = {
337
+ ...baseEnv,
338
+ ...normalizedConfig,
339
+ };
340
+ }
231
341
  }
232
342
 
233
343
  // 获取服务器清单逻辑
@@ -251,7 +361,7 @@ export const createMCPPluginStoreSlice: StateCreator<
251
361
  {
252
362
  args: connection.args,
253
363
  command: connection.command!,
254
- env: config,
364
+ env: mergedStdioEnv,
255
365
  name: identifier, // 将配置作为环境变量传递(resume 模式下)
256
366
  },
257
367
  { avatar: plugin.icon, description: plugin.description, name: data.name },
@@ -261,6 +371,8 @@ export const createMCPPluginStoreSlice: StateCreator<
261
371
  if (connection?.type === 'http') {
262
372
  manifest = await mcpService.getStreamableMcpServerManifest(
263
373
  {
374
+ auth: connection.auth,
375
+ headers: mergedHttpHeaders,
264
376
  identifier,
265
377
  metadata: {
266
378
  avatar: plugin.icon,
@@ -318,7 +430,7 @@ export const createMCPPluginStoreSlice: StateCreator<
318
430
  customParams: { mcp: connection },
319
431
  identifier: plugin.identifier,
320
432
  manifest: manifest,
321
- settings: config,
433
+ settings: normalizedConfig,
322
434
  type: 'plugin',
323
435
  });
324
436
 
@@ -347,7 +459,7 @@ export const createMCPPluginStoreSlice: StateCreator<
347
459
  resources: (manifest as any).resources,
348
460
  tools: (manifest as any).tools,
349
461
  },
350
- platform: result!.platform,
462
+ platform: result?.platform || process.platform,
351
463
  success: true,
352
464
  userAgent,
353
465
  version: manifest.version || data.version,
@@ -423,7 +535,7 @@ export const createMCPPluginStoreSlice: StateCreator<
423
535
  installDurationMs,
424
536
  installParams: connection,
425
537
  metadata: errorInfo.metadata,
426
- platform: result!.platform,
538
+ platform: result?.platform || process.platform,
427
539
  success: false,
428
540
  userAgent,
429
541
  version: data?.version,
@@ -581,10 +693,22 @@ export const createMCPPluginStoreSlice: StateCreator<
581
693
 
582
694
  useFetchMCPPluginList: (params) => {
583
695
  const locale = globalHelpers.getCurrentLanguage();
696
+ const requestParams = isDesktop ? params : { ...params, connectionType: McpConnectionType.http };
697
+ const swrKeyParts = [
698
+ 'useFetchMCPPluginList',
699
+ locale,
700
+ requestParams.page,
701
+ requestParams.pageSize,
702
+ requestParams.q,
703
+ requestParams.connectionType,
704
+ ];
705
+ const swrKey = swrKeyParts.filter((part) => part !== undefined && part !== null && part !== '')
706
+ .join('-');
707
+ const page = requestParams.page ?? 1;
584
708
 
585
709
  return useSWR<PluginListResponse>(
586
- ['useFetchMCPPluginList', locale, ...Object.values(params)].filter(Boolean).join('-'),
587
- () => discoverService.getMCPPluginList(params),
710
+ swrKey,
711
+ () => discoverService.getMCPPluginList(requestParams),
588
712
  {
589
713
  onSuccess(data) {
590
714
  set(
@@ -602,7 +726,7 @@ export const createMCPPluginStoreSlice: StateCreator<
602
726
  }
603
727
 
604
728
  // 累积数据逻辑
605
- if (params.page === 1) {
729
+ if (page === 1) {
606
730
  // 第一页,直接设置
607
731
  draft.mcpPluginItems = uniqBy(data.items, 'identifier');
608
732
  } else {
@@ -1,4 +1,3 @@
1
- import { isDesktop } from '@/const/version';
2
1
  import { DiscoverPluginItem } from '@/types/discover';
3
2
 
4
3
  export type PluginInstallLoadingMap = Record<string, boolean | undefined>;
@@ -47,7 +46,7 @@ export const initialPluginStoreState: PluginStoreState = {
47
46
  // Plugin list state management initial values
48
47
  currentPluginPage: 1,
49
48
  displayMode: 'grid',
50
- listType: isDesktop ? PluginStoreTabs.MCP : PluginStoreTabs.Plugin,
49
+ listType: PluginStoreTabs.MCP,
51
50
  oldPluginItems: [],
52
51
  pluginInstallLoading: {},
53
52
  pluginInstallProgress: {},